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

Revision 2182, 21.7 KB checked in by lowjoel, 20 months ago (diff)

Supplements r2170: some functions can be used when the drive is not mounted.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.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                ulong freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
427                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out freeBytesAvailable,
428                    out totalNumberOfBytes, out totalNumberOfFreeBytes))
429                {
430                    return totalNumberOfFreeBytes != freeBytesAvailable;
431                }
432                else if (Marshal.GetLastWin32Error() == 21 /*ERROR_NOT_READY*/)
433                {
434                    //For the lack of more appropriate responses.
435                    return false;
436                }
437
438                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
439            }
440        }
441
442        /// <summary>
443        /// Gets a value indicating whether a drive is ready.
444        /// </summary>
445        public bool IsReady { get; private set; }
446
447        /// <summary>
448        /// Gets the total amount of free space available on a drive.
449        /// </summary>
450        public long TotalFreeSpace
451        {
452            get
453            {
454                if (!IsReady)
455                    throw new InvalidOperationException("The volume has not been mounted or is not " +
456                        "currently ready.");
457
458                ulong result, dummy;
459                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
460                    out dummy, out result))
461                {
462                    return (long)result;
463                }
464
465                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
466            }
467        }
468       
469        /// <summary>
470        /// Gets the total size of storage space on a drive.
471        /// </summary>
472        public long TotalSize
473        {
474            get
475            {
476                if (!IsReady)
477                    throw new InvalidOperationException("The volume has not been mounted or is not " +
478                        "currently ready.");
479
480                ulong result, dummy;
481                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
482                    out result, out dummy))
483                {
484                    return (long)result;
485                }
486
487                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
488            }
489        }
490
491        /// <summary>
492        /// Indicates the amount of available free space on a drive.
493        /// </summary>
494        public long AvailableFreeSpace
495        {
496            get
497            {
498                if (!IsReady)
499                    throw new InvalidOperationException("The volume has not been mounted or is not " +
500                        "currently ready.");
501
502                ulong result, dummy;
503                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out result,
504                    out dummy, out dummy))
505                {
506                    return (long)result;
507                }
508
509                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
510            }
511        }
512
513        /// <summary>
514        /// Retrieves all mountpoints in the current volume, if the current volume
515        /// contains volume mountpoints.
516        /// </summary>
517        public ICollection<VolumeInfo> MountedVolumes
518        {
519            get
520            {
521                if (!IsReady)
522                    throw new InvalidOperationException("The volume has not been mounted or is not " +
523                        "currently ready.");
524
525                List<VolumeInfo> result = new List<VolumeInfo>();
526                StringBuilder nextMountpoint = new StringBuilder(
527                    KernelApi.NativeMethods.LongPath * sizeof(char));
528
529                SafeHandle handle = KernelApi.NativeMethods.FindFirstVolumeMountPoint(VolumeId,
530                    nextMountpoint, KernelApi.NativeMethods.LongPath);
531                if (handle.IsInvalid)
532                    return result;
533
534                //Iterate over the volume mountpoints
535                while (KernelApi.NativeMethods.FindNextVolumeMountPoint(handle,
536                    nextMountpoint, KernelApi.NativeMethods.LongPath))
537                {
538                    result.Add(new VolumeInfo(nextMountpoint.ToString()));
539                }
540
541                //Close the handle
542                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
543                    KernelApi.NativeMethods.FindVolumeMountPointClose(handle);
544
545                return result.AsReadOnly();
546            }
547        }
548
549        /// <summary>
550        /// The various mountpoints to the root of the volume. This list contains
551        /// paths which may be a drive or a mountpoint. Every string includes the
552        /// trailing backslash.
553        /// </summary>
554        public ReadOnlyCollection<string> MountPoints
555        {
556            get
557            {
558                return (VolumeType == DriveType.Network ?
559                    GetNetworkMountPoints() : GetLocalVolumeMountPoints()).AsReadOnly();
560            }
561        }
562
563        /// <summary>
564        /// Gets whether the current volume is mounted at any place.
565        /// </summary>
566        public bool IsMounted
567        {
568            get { return MountPoints.Count != 0; }
569        }
570
571        /// <summary>
572        /// Opens a file with read, write, or read/write access.
573        /// </summary>
574        /// <param name="access">A System.IO.FileAccess constant specifying whether
575        /// to open the file with Read, Write, or ReadWrite file access.</param>
576        /// <returns>A System.IO.FileStream object opened in the specified mode
577        /// and access, unshared, and no special file options.</returns>
578        public FileStream Open(FileAccess access)
579        {
580            return Open(access, FileShare.None, FileOptions.None);
581        }
582
583        /// <summary>
584        /// Opens a file with read, write, or read/write access and the specified
585        /// sharing option.
586        /// </summary>
587        /// <param name="access">A System.IO.FileAccess constant specifying whether
588        /// to open the file with Read, Write, or ReadWrite file access.</param>
589        /// <param name="share">A System.IO.FileShare constant specifying the type
590        /// of access other FileStream objects have to this file.</param>
591        /// <returns>A System.IO.FileStream object opened with the specified mode,
592        /// access, sharing options, and no special file options.</returns>
593        public FileStream Open(FileAccess access, FileShare share)
594        {
595            return Open(access, share, FileOptions.None);
596        }
597
598        /// <summary>
599        /// Opens a file with read, write, or read/write access, the specified
600        /// sharing option, and other advanced options.
601        /// </summary>
602        /// <param name="mode">A System.IO.FileMode constant specifying the mode
603        /// (for example, Open or Append) in which to open the file.</param>
604        /// <param name="access">A System.IO.FileAccess constant specifying whether
605        /// to open the file with Read, Write, or ReadWrite file access.</param>
606        /// <param name="share">A System.IO.FileShare constant specifying the type
607        /// of access other FileStream objects have to this file.</param>
608        /// <param name="options">The System.IO.FileOptions constant specifying
609        /// the advanced file options to use when opening the file.</param>
610        /// <returns>A System.IO.FileStream object opened with the specified mode,
611        /// access, sharing options, and special file options.</returns>
612        public FileStream Open(FileAccess access, FileShare share, FileOptions options)
613        {
614            SafeFileHandle handle = OpenHandle(access, share, options);
615
616            //Check that the handle is valid
617            if (handle.IsInvalid)
618                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
619
620            //Return the FileStream
621            return new FileStream(handle, access);
622        }
623
624        private SafeFileHandle OpenHandle(FileAccess access, FileShare share, FileOptions options)
625        {
626            //Access mode
627            uint iAccess = 0;
628            switch (access)
629            {
630                case FileAccess.Read:
631                    iAccess = KernelApi.NativeMethods.GENERIC_READ;
632                    break;
633                case FileAccess.ReadWrite:
634                    iAccess = KernelApi.NativeMethods.GENERIC_READ |
635                        KernelApi.NativeMethods.GENERIC_WRITE;
636                    break;
637                case FileAccess.Write:
638                    iAccess = KernelApi.NativeMethods.GENERIC_WRITE;
639                    break;
640            }
641
642            //Sharing mode
643            if ((share & FileShare.Inheritable) != 0)
644                throw new NotSupportedException("Inheritable handles are not supported.");
645
646            //Advanced options
647            if ((options & FileOptions.Asynchronous) != 0)
648                throw new NotSupportedException("Asynchronous handles are not implemented.");
649
650            //Create the handle
651            string openPath = VolumeId;
652            if (openPath.Length > 0 && openPath[openPath.Length - 1] == '\\')
653                openPath = openPath.Remove(openPath.Length - 1);
654            SafeFileHandle result = KernelApi.NativeMethods.CreateFile(openPath, iAccess,
655                (uint)share, IntPtr.Zero, (uint)FileMode.Open, (uint)options, IntPtr.Zero);
656            if (result.IsInvalid)
657                throw KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
658
659            return result;
660        }
661
662        public VolumeLock LockVolume(FileStream stream)
663        {
664            return new VolumeLock(stream);
665        }
666    }
667
668    public class VolumeLock : IDisposable
669    {
670        internal VolumeLock(FileStream stream)
671        {
672            uint result = 0;
673            for (int i = 0; !KernelApi.NativeMethods.DeviceIoControl(stream.SafeFileHandle,
674                    KernelApi.NativeMethods.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
675                    0, out result, IntPtr.Zero); ++i)
676            {
677                if (i > 100)
678                    throw new IOException("Could not lock volume.",
679                        KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error()));
680                System.Threading.Thread.Sleep(100);
681            }
682
683            Stream = stream;
684        }
685
686        ~VolumeLock()
687        {
688            Dispose(false);
689        }
690
691        public void Dispose()
692        {
693            Dispose(true);
694        }
695
696        void Dispose(bool disposing)
697        {
698            if (disposing)
699                GC.SuppressFinalize(this);
700
701            //Flush the contents of the buffer to disk since after we unlock the volume
702            //we can no longer write to the volume.
703            Stream.Flush();
704
705            uint result = 0;
706            if (!KernelApi.NativeMethods.DeviceIoControl(Stream.SafeFileHandle,
707                KernelApi.NativeMethods.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
708                0, out result, IntPtr.Zero))
709            {
710                throw new IOException("Could not unlock volume.");
711            }
712        }
713
714        private FileStream Stream;
715    }
716}
Note: See TracBrowser for help on using the repository browser.