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

Revision 2176, 21.8 KB checked in by lowjoel, 5 years ago (diff)

Fixed potential ArgumentException? when a network path is simply remembered, 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            List<string> result = new List<string>();
94
95            //Get the paths of the said volume
96            IntPtr pathNamesBuffer = IntPtr.Zero;
97            string pathNames = string.Empty;
98            try
99            {
100                uint currentBufferSize = KernelApi.NativeMethods.MaxPath;
101                uint returnLength = 0;
102                pathNamesBuffer = Marshal.AllocHGlobal((int)(currentBufferSize * sizeof(char)));
103                while (!KernelApi.NativeMethods.GetVolumePathNamesForVolumeName(VolumeId,
104                    pathNamesBuffer, currentBufferSize, out returnLength))
105                {
106                    if (Marshal.GetLastWin32Error() == 234/*ERROR_MORE_DATA*/)
107                    {
108                        Marshal.FreeHGlobal(pathNamesBuffer);
109                        currentBufferSize *= 2;
110                        pathNamesBuffer = Marshal.AllocHGlobal((int)(currentBufferSize * sizeof(char)));
111                    }
112                    else if (Marshal.GetLastWin32Error() == 21 /*ERROR_NOT_READY*/)
113                        return result;
114                    else
115                        throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
116                }
117
118                pathNames = Marshal.PtrToStringUni(pathNamesBuffer, (int)returnLength);
119            }
120            finally
121            {
122                if (pathNamesBuffer != IntPtr.Zero)
123                    Marshal.FreeHGlobal(pathNamesBuffer);
124            }
125
126            //OK, the marshalling is complete. Convert the pathNames string into a list
127            //of strings containing all of the volumes mountpoints; because the
128            //GetVolumePathNamesForVolumeName function returns a convoluted structure
129            //containing the path names.
130            for (int lastIndex = 0, i = 0; i != pathNames.Length; ++i)
131            {
132                if (pathNames[i] == '\0')
133                {
134                    //If there are no mount points for this volume, the string will only
135                    //have one NULL
136                    if (i - lastIndex == 0)
137                        break;
138
139                    result.Add(pathNames.Substring(lastIndex, i - lastIndex));
140
141                    lastIndex = i + 1;
142                    if (pathNames[lastIndex] == '\0')
143                        break;
144                }
145            }
146
147            return result;
148        }
149
150        /// <summary>
151        /// Gets the mountpoints associated with the network share.
152        /// </summary>
153        /// <returns>A list of network mount points for the given network share.</returns>
154        private List<string> GetNetworkMountPoints()
155        {
156            List<string> result = new List<string>();
157
158            //Open an enumeration handle to list mount points.
159            IntPtr enumHandle;
160            uint errorCode = KernelApi.NativeMethods.WNetOpenEnum(
161                KernelApi.NativeMethods.RESOURCE_CONNECTED,
162                KernelApi.NativeMethods.RESOURCETYPE_DISK, 0, IntPtr.Zero, out enumHandle);
163            if (errorCode != 0 /*ERROR_SUCCESS*/)
164                throw new Win32Exception((int)errorCode);
165
166            try
167            {
168                int resultBufferCount = 32;
169                int resultBufferSize = resultBufferCount *
170                    Marshal.SizeOf(typeof(KernelApi.NativeMethods.NETRESOURCE));
171                IntPtr resultBuffer = Marshal.AllocHGlobal(resultBufferSize);
172
173                try
174                {
175                    for ( ; ; )
176                    {
177                        uint resultBufferStored = (uint)resultBufferCount;
178                        uint resultBufferRequiredSize = (uint)resultBufferSize;
179                        errorCode = KernelApi.NativeMethods.WNetEnumResource(
180                            enumHandle, ref resultBufferStored, resultBuffer,
181                            ref resultBufferRequiredSize);
182
183                        if (errorCode == 259 /*ERROR_NO_MORE_ITEMS*/)
184                            break;
185                        else if (errorCode != 0 /*ERROR_SUCCESS*/)
186                            throw new Win32Exception((int)errorCode);
187
188                        unsafe
189                        {
190                            //Marshal the memory block to managed structures.
191                            byte* pointer = (byte*)resultBuffer.ToPointer();
192
193                            for (uint i = 0; i < resultBufferStored;
194                                ++i, pointer += Marshal.SizeOf(typeof(KernelApi.NativeMethods.NETRESOURCE)))
195                            {
196                                KernelApi.NativeMethods.NETRESOURCE resource =
197                                    (KernelApi.NativeMethods.NETRESOURCE)Marshal.PtrToStructure(
198                                        (IntPtr)pointer, typeof(KernelApi.NativeMethods.NETRESOURCE));
199
200                                //Skip all UNC paths without a local mount point
201                                if (resource.lpLocalName == null)
202                                    continue;
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                return (DriveType)KernelApi.NativeMethods.GetDriveType(VolumeId);
372            }
373        }
374
375        /// <summary>
376        /// Determines the cluster size of the current volume.
377        /// </summary>
378        public int ClusterSize
379        {
380            get
381            {
382                if (!IsReady)
383                    throw new InvalidOperationException("The volume has not been mounted or is not " +
384                        "currently ready.");
385
386                uint clusterSize, sectorSize, freeClusters, totalClusters;
387                if (KernelApi.NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
388                    out sectorSize, out freeClusters, out totalClusters))
389                {
390                    return (int)(clusterSize * sectorSize);
391                }
392
393                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
394            }
395        }
396
397        /// <summary>
398        /// Determines the sector size of the current volume.
399        /// </summary>
400        public int SectorSize
401        {
402            get
403            {
404                if (!IsReady)
405                    throw new InvalidOperationException("The volume has not been mounted or is not " +
406                        "currently ready.");
407
408                uint clusterSize, sectorSize, freeClusters, totalClusters;
409                if (KernelApi.NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
410                    out sectorSize, out freeClusters, out totalClusters))
411                {
412                    return (int)sectorSize;
413                }
414
415                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
416            }
417        }
418
419        /// <summary>
420        /// Checks if the current user has disk quotas on the current volume.
421        /// </summary>
422        public bool HasQuota
423        {
424            get
425            {
426                if (!IsReady)
427                    throw new InvalidOperationException("The volume has not been mounted or is not " +
428                        "currently ready.");
429
430                ulong freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
431                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out freeBytesAvailable,
432                    out totalNumberOfBytes, out totalNumberOfFreeBytes))
433                {
434                    return totalNumberOfFreeBytes != freeBytesAvailable;
435                }
436                else if (Marshal.GetLastWin32Error() == 21 /*ERROR_NOT_READY*/)
437                {
438                    //For the lack of more appropriate responses.
439                    return false;
440                }
441
442                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
443            }
444        }
445
446        /// <summary>
447        /// Gets a value indicating whether a drive is ready.
448        /// </summary>
449        public bool IsReady { get; private set; }
450
451        /// <summary>
452        /// Gets the total amount of free space available on a drive.
453        /// </summary>
454        public long TotalFreeSpace
455        {
456            get
457            {
458                if (!IsReady)
459                    throw new InvalidOperationException("The volume has not been mounted or is not " +
460                        "currently ready.");
461
462                ulong result, dummy;
463                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
464                    out dummy, out result))
465                {
466                    return (long)result;
467                }
468
469                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
470            }
471        }
472       
473        /// <summary>
474        /// Gets the total size of storage space on a drive.
475        /// </summary>
476        public long TotalSize
477        {
478            get
479            {
480                if (!IsReady)
481                    throw new InvalidOperationException("The volume has not been mounted or is not " +
482                        "currently ready.");
483
484                ulong result, dummy;
485                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
486                    out result, out dummy))
487                {
488                    return (long)result;
489                }
490
491                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
492            }
493        }
494
495        /// <summary>
496        /// Indicates the amount of available free space on a drive.
497        /// </summary>
498        public long AvailableFreeSpace
499        {
500            get
501            {
502                if (!IsReady)
503                    throw new InvalidOperationException("The volume has not been mounted or is not " +
504                        "currently ready.");
505
506                ulong result, dummy;
507                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out result,
508                    out dummy, out dummy))
509                {
510                    return (long)result;
511                }
512
513                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
514            }
515        }
516
517        /// <summary>
518        /// Retrieves all mountpoints in the current volume, if the current volume
519        /// contains volume mountpoints.
520        /// </summary>
521        public ICollection<VolumeInfo> MountedVolumes
522        {
523            get
524            {
525                if (!IsReady)
526                    throw new InvalidOperationException("The volume has not been mounted or is not " +
527                        "currently ready.");
528
529                List<VolumeInfo> result = new List<VolumeInfo>();
530                StringBuilder nextMountpoint = new StringBuilder(
531                    KernelApi.NativeMethods.LongPath * sizeof(char));
532
533                SafeHandle handle = KernelApi.NativeMethods.FindFirstVolumeMountPoint(VolumeId,
534                    nextMountpoint, KernelApi.NativeMethods.LongPath);
535                if (handle.IsInvalid)
536                    return result;
537
538                //Iterate over the volume mountpoints
539                while (KernelApi.NativeMethods.FindNextVolumeMountPoint(handle,
540                    nextMountpoint, KernelApi.NativeMethods.LongPath))
541                {
542                    result.Add(new VolumeInfo(nextMountpoint.ToString()));
543                }
544
545                //Close the handle
546                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
547                    KernelApi.NativeMethods.FindVolumeMountPointClose(handle);
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 ReadOnlyCollection<string> MountPoints
559        {
560            get
561            {
562                return (VolumeType == DriveType.Network ?
563                    GetNetworkMountPoints() : GetLocalVolumeMountPoints()).AsReadOnly();
564            }
565        }
566
567        /// <summary>
568        /// Gets whether the current volume is mounted at any place.
569        /// </summary>
570        public bool IsMounted
571        {
572            get { return MountPoints.Count != 0; }
573        }
574
575        /// <summary>
576        /// Opens a file with read, write, or read/write access.
577        /// </summary>
578        /// <param name="access">A System.IO.FileAccess constant specifying whether
579        /// to open the file with Read, Write, or ReadWrite file access.</param>
580        /// <returns>A System.IO.FileStream object opened in the specified mode
581        /// and access, unshared, and no special file options.</returns>
582        public FileStream Open(FileAccess access)
583        {
584            return Open(access, FileShare.None, FileOptions.None);
585        }
586
587        /// <summary>
588        /// Opens a file with read, write, or read/write access and the specified
589        /// sharing option.
590        /// </summary>
591        /// <param name="access">A System.IO.FileAccess constant specifying whether
592        /// to open the file with Read, Write, or ReadWrite file access.</param>
593        /// <param name="share">A System.IO.FileShare constant specifying the type
594        /// of access other FileStream objects have to this file.</param>
595        /// <returns>A System.IO.FileStream object opened with the specified mode,
596        /// access, sharing options, and no special file options.</returns>
597        public FileStream Open(FileAccess access, FileShare share)
598        {
599            return Open(access, share, FileOptions.None);
600        }
601
602        /// <summary>
603        /// Opens a file with read, write, or read/write access, the specified
604        /// sharing option, and other advanced options.
605        /// </summary>
606        /// <param name="mode">A System.IO.FileMode constant specifying the mode
607        /// (for example, Open or Append) in which to open the file.</param>
608        /// <param name="access">A System.IO.FileAccess constant specifying whether
609        /// to open the file with Read, Write, or ReadWrite file access.</param>
610        /// <param name="share">A System.IO.FileShare constant specifying the type
611        /// of access other FileStream objects have to this file.</param>
612        /// <param name="options">The System.IO.FileOptions constant specifying
613        /// the advanced file options to use when opening the file.</param>
614        /// <returns>A System.IO.FileStream object opened with the specified mode,
615        /// access, sharing options, and special file options.</returns>
616        public FileStream Open(FileAccess access, FileShare share, FileOptions options)
617        {
618            SafeFileHandle handle = OpenHandle(access, share, options);
619
620            //Check that the handle is valid
621            if (handle.IsInvalid)
622                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
623
624            //Return the FileStream
625            return new FileStream(handle, access);
626        }
627
628        private SafeFileHandle OpenHandle(FileAccess access, FileShare share, FileOptions options)
629        {
630            //Access mode
631            uint iAccess = 0;
632            switch (access)
633            {
634                case FileAccess.Read:
635                    iAccess = KernelApi.NativeMethods.GENERIC_READ;
636                    break;
637                case FileAccess.ReadWrite:
638                    iAccess = KernelApi.NativeMethods.GENERIC_READ |
639                        KernelApi.NativeMethods.GENERIC_WRITE;
640                    break;
641                case FileAccess.Write:
642                    iAccess = KernelApi.NativeMethods.GENERIC_WRITE;
643                    break;
644            }
645
646            //Sharing mode
647            if ((share & FileShare.Inheritable) != 0)
648                throw new NotSupportedException("Inheritable handles are not supported.");
649
650            //Advanced options
651            if ((options & FileOptions.Asynchronous) != 0)
652                throw new NotSupportedException("Asynchronous handles are not implemented.");
653
654            //Create the handle
655            string openPath = VolumeId;
656            if (openPath.Length > 0 && openPath[openPath.Length - 1] == '\\')
657                openPath = openPath.Remove(openPath.Length - 1);
658            SafeFileHandle result = KernelApi.NativeMethods.CreateFile(openPath, iAccess,
659                (uint)share, IntPtr.Zero, (uint)FileMode.Open, (uint)options, IntPtr.Zero);
660            if (result.IsInvalid)
661                throw KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
662
663            return result;
664        }
665
666        public VolumeLock LockVolume(FileStream stream)
667        {
668            return new VolumeLock(stream);
669        }
670    }
671
672    public class VolumeLock : IDisposable
673    {
674        internal VolumeLock(FileStream stream)
675        {
676            uint result = 0;
677            for (int i = 0; !KernelApi.NativeMethods.DeviceIoControl(stream.SafeFileHandle,
678                    KernelApi.NativeMethods.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
679                    0, out result, IntPtr.Zero); ++i)
680            {
681                if (i > 100)
682                    throw new IOException("Could not lock volume.",
683                        KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error()));
684                System.Threading.Thread.Sleep(100);
685            }
686
687            Stream = stream;
688        }
689
690        ~VolumeLock()
691        {
692            Dispose(false);
693        }
694
695        public void Dispose()
696        {
697            Dispose(true);
698        }
699
700        void Dispose(bool disposing)
701        {
702            if (disposing)
703                GC.SuppressFinalize(this);
704
705            //Flush the contents of the buffer to disk since after we unlock the volume
706            //we can no longer write to the volume.
707            Stream.Flush();
708
709            uint result = 0;
710            if (!KernelApi.NativeMethods.DeviceIoControl(Stream.SafeFileHandle,
711                KernelApi.NativeMethods.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
712                0, out result, IntPtr.Zero))
713            {
714                throw new IOException("Could not unlock volume.");
715            }
716        }
717
718        private FileStream Stream;
719    }
720}
Note: See TracBrowser for help on using the repository browser.