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

Revision 2146, 20.8 KB checked in by lowjoel, 4 years ago (diff)

Allow erasing of UNC paths.

  • 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                                //Ensure that the path in the resource structure ends with a trailing
201                                //backslash as out volume ID ends with one.
202                                if (string.IsNullOrEmpty(resource.lpRemoteName))
203                                    continue;
204                                if (resource.lpRemoteName[resource.lpRemoteName.Length - 1] != '\\')
205                                    resource.lpRemoteName += '\\';
206
207                                if (resource.lpRemoteName == VolumeId)
208                                    result.Add(resource.lpLocalName);
209                            }
210                        }
211                    }
212                }
213                finally
214                {
215                    Marshal.FreeHGlobal(resultBuffer);
216                }
217            }
218            finally
219            {
220                KernelApi.NativeMethods.WNetCloseEnum(enumHandle);
221            }
222
223            return result;
224        }
225
226        /// <summary>
227        /// Lists all the volumes in the system.
228        /// </summary>
229        /// <returns>Returns a list of volumes representing all volumes present in
230        /// the system.</returns>
231        public static ICollection<VolumeInfo> Volumes
232        {
233            get
234            {
235                List<VolumeInfo> result = new List<VolumeInfo>();
236                StringBuilder nextVolume = new StringBuilder(
237                    KernelApi.NativeMethods.LongPath * sizeof(char));
238                SafeHandle handle = KernelApi.NativeMethods.FindFirstVolume(nextVolume,
239                    KernelApi.NativeMethods.LongPath);
240                if (handle.IsInvalid)
241                    return result;
242
243                //Iterate over the volume mountpoints
244                do
245                    result.Add(new VolumeInfo(nextVolume.ToString()));
246                while (KernelApi.NativeMethods.FindNextVolume(handle, nextVolume,
247                    KernelApi.NativeMethods.LongPath));
248
249                //Close the handle
250                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
251                    KernelApi.NativeMethods.FindVolumeClose(handle);
252
253                return result.AsReadOnly();
254            }
255        }
256
257        /// <summary>
258        /// Creates a Volume object from its mountpoint.
259        /// </summary>
260        /// <param name="mountpoint">The path to the mountpoint.</param>
261        /// <returns>The volume object if such a volume exists, or an exception
262        /// is thrown.</returns>
263        public static VolumeInfo FromMountpoint(string mountpoint)
264        {
265            DirectoryInfo mountpointDir = new DirectoryInfo(mountpoint);
266           
267            //Verify that the mountpoint given exists; if it doesn't we'll raise
268            //a DirectoryNotFound exception.
269            if (!mountpointDir.Exists)
270                throw new DirectoryNotFoundException();
271
272            do
273            {
274                //Ensure that the current path has a trailing backslash
275                string currentDir = mountpointDir.FullName;
276                if (currentDir.Length > 0 && currentDir[currentDir.Length - 1] != '\\')
277                    currentDir += '\\';
278
279                //The path cannot be empty.
280                if (string.IsNullOrEmpty(currentDir))
281                    throw new DirectoryNotFoundException();
282
283                //Get the type of the drive
284                DriveType driveType = (DriveType)KernelApi.NativeMethods.GetDriveType(currentDir);
285
286                //We do different things for different kinds of drives. Network drives
287                //will need us to resolve the drive to a UNC path. Local drives will
288                //be resolved to a volume GUID
289                StringBuilder volumeID = new StringBuilder(KernelApi.NativeMethods.MaxPath);
290                if (driveType == DriveType.Network)
291                {
292                    //If the current directory is a UNC path, then return the VolumeInfo instance
293                    //directly
294                    if (currentDir.Substring(0, 2) == "\\\\" && currentDir.IndexOf('\\', 2) != -1)
295                        return new VolumeInfo(currentDir);
296
297                    //Otherwise, resolve the mountpoint to a UNC path
298                    uint bufferCapacity = (uint)volumeID.Capacity;
299                    uint errorCode = KernelApi.NativeMethods.WNetGetConnection(
300                        currentDir.Substring(0, currentDir.Length - 1),
301                        volumeID, ref bufferCapacity);
302
303                    switch (errorCode)
304                    {
305                        case 0: //ERROR_SUCCESS
306                            return new VolumeInfo(volumeID.ToString() + '\\');
307
308                        case 1200: //ERROR_BAD_DEVICE: path is not a network share
309                            break;
310
311                        default:
312                            throw new Win32Exception((int)errorCode);
313                    }
314                }
315                else
316                {
317                    if (KernelApi.NativeMethods.GetVolumeNameForVolumeMountPoint(
318                        currentDir, volumeID, 50))
319                    {
320                        return new VolumeInfo(volumeID.ToString());
321                    }
322                    else
323                    {
324                        switch (Marshal.GetLastWin32Error())
325                        {
326                            case 1: //ERROR_INVALID_FUNCTION
327                            case 2: //ERROR_FILE_NOT_FOUND
328                            case 3: //ERROR_PATH_NOT_FOUND
329                            case 4390: //ERROR_NOT_A_REPARSE_POINT
330                                break;
331                            default:
332                                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
333                        }
334                    }
335                }
336
337                mountpointDir = mountpointDir.Parent;
338            }
339            while (mountpointDir != null);
340
341            throw Marshal.GetExceptionForHR(KernelApi.GetHRForWin32Error(
342                4390 /*ERROR_NOT_A_REPARSE_POINT*/));
343        }
344
345        /// <summary>
346        /// Returns the volume identifier as would be returned from FindFirstVolume.
347        /// </summary>
348        public string VolumeId { get; private set; }
349
350        /// <summary>
351        /// Gets or sets the volume label of a drive.
352        /// </summary>
353        public string VolumeLabel { get; private set; }
354
355        /// <summary>
356        /// Gets the name of the file system, such as NTFS or FAT32.
357        /// </summary>
358        public string VolumeFormat { get; private set; }
359
360        /// <summary>
361        /// Gets the drive type; returns one of the System.IO.DriveType values.
362        /// </summary>
363        public DriveType VolumeType
364        {
365            get
366            {
367                return (DriveType)KernelApi.NativeMethods.GetDriveType(VolumeId);
368            }
369        }
370
371        /// <summary>
372        /// Determines the cluster size of the current volume.
373        /// </summary>
374        public int ClusterSize
375        {
376            get
377            {
378                uint clusterSize, sectorSize, freeClusters, totalClusters;
379                if (KernelApi.NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
380                    out sectorSize, out freeClusters, out totalClusters))
381                {
382                    return (int)(clusterSize * sectorSize);
383                }
384
385                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
386            }
387        }
388
389        /// <summary>
390        /// Determines the sector size of the current volume.
391        /// </summary>
392        public int SectorSize
393        {
394            get
395            {
396                uint clusterSize, sectorSize, freeClusters, totalClusters;
397                if (KernelApi.NativeMethods.GetDiskFreeSpace(VolumeId, out clusterSize,
398                    out sectorSize, out freeClusters, out totalClusters))
399                {
400                    return (int)sectorSize;
401                }
402
403                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
404            }
405        }
406
407        /// <summary>
408        /// Checks if the current user has disk quotas on the current volume.
409        /// </summary>
410        public bool HasQuota
411        {
412            get
413            {
414                ulong freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
415                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out freeBytesAvailable,
416                    out totalNumberOfBytes, out totalNumberOfFreeBytes))
417                {
418                    return totalNumberOfFreeBytes != freeBytesAvailable;
419                }
420                else if (Marshal.GetLastWin32Error() == 21 /*ERROR_NOT_READY*/)
421                {
422                    //For the lack of more appropriate responses.
423                    return false;
424                }
425
426                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
427            }
428        }
429
430        /// <summary>
431        /// Gets a value indicating whether a drive is ready.
432        /// </summary>
433        public bool IsReady { get; private set; }
434
435        /// <summary>
436        /// Gets the total amount of free space available on a drive.
437        /// </summary>
438        public long TotalFreeSpace
439        {
440            get
441            {
442                ulong result, dummy;
443                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
444                    out dummy, out result))
445                {
446                    return (long)result;
447                }
448
449                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
450            }
451        }
452       
453        /// <summary>
454        /// Gets the total size of storage space on a drive.
455        /// </summary>
456        public long TotalSize
457        {
458            get
459            {
460                ulong result, dummy;
461                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out dummy,
462                    out result, out dummy))
463                {
464                    return (long)result;
465                }
466
467                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
468            }
469        }
470
471        /// <summary>
472        /// Indicates the amount of available free space on a drive.
473        /// </summary>
474        public long AvailableFreeSpace
475        {
476            get
477            {
478                ulong result, dummy;
479                if (KernelApi.NativeMethods.GetDiskFreeSpaceEx(VolumeId, out result,
480                    out dummy, out dummy))
481                {
482                    return (long)result;
483                }
484
485                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
486            }
487        }
488
489        /// <summary>
490        /// Retrieves all mountpoints in the current volume, if the current volume
491        /// contains volume mountpoints.
492        /// </summary>
493        public ICollection<VolumeInfo> MountedVolumes
494        {
495            get
496            {
497                List<VolumeInfo> result = new List<VolumeInfo>();
498                StringBuilder nextMountpoint = new StringBuilder(
499                    KernelApi.NativeMethods.LongPath * sizeof(char));
500
501                SafeHandle handle = KernelApi.NativeMethods.FindFirstVolumeMountPoint(VolumeId,
502                    nextMountpoint, KernelApi.NativeMethods.LongPath);
503                if (handle.IsInvalid)
504                    return result;
505
506                //Iterate over the volume mountpoints
507                while (KernelApi.NativeMethods.FindNextVolumeMountPoint(handle,
508                    nextMountpoint, KernelApi.NativeMethods.LongPath))
509                {
510                    result.Add(new VolumeInfo(nextMountpoint.ToString()));
511                }
512
513                //Close the handle
514                if (Marshal.GetLastWin32Error() == 18 /*ERROR_NO_MORE_FILES*/)
515                    KernelApi.NativeMethods.FindVolumeMountPointClose(handle);
516
517                return result.AsReadOnly();
518            }
519        }
520
521        /// <summary>
522        /// The various mountpoints to the root of the volume. This list contains
523        /// paths which may be a drive or a mountpoint. Every string includes the
524        /// trailing backslash.
525        /// </summary>
526        public ReadOnlyCollection<string> MountPoints
527        {
528            get
529            {
530                return (VolumeType == DriveType.Network ?
531                    GetNetworkMountPoints() : GetLocalVolumeMountPoints()).AsReadOnly();
532            }
533        }
534
535        /// <summary>
536        /// Gets whether the current volume is mounted at any place.
537        /// </summary>
538        public bool IsMounted
539        {
540            get { return MountPoints.Count != 0; }
541        }
542
543        /// <summary>
544        /// Opens a file with read, write, or read/write access.
545        /// </summary>
546        /// <param name="access">A System.IO.FileAccess constant specifying whether
547        /// to open the file with Read, Write, or ReadWrite file access.</param>
548        /// <returns>A System.IO.FileStream object opened in the specified mode
549        /// and access, unshared, and no special file options.</returns>
550        public FileStream Open(FileAccess access)
551        {
552            return Open(access, FileShare.None, FileOptions.None);
553        }
554
555        /// <summary>
556        /// Opens a file with read, write, or read/write access and the specified
557        /// sharing option.
558        /// </summary>
559        /// <param name="access">A System.IO.FileAccess constant specifying whether
560        /// to open the file with Read, Write, or ReadWrite file access.</param>
561        /// <param name="share">A System.IO.FileShare constant specifying the type
562        /// of access other FileStream objects have to this file.</param>
563        /// <returns>A System.IO.FileStream object opened with the specified mode,
564        /// access, sharing options, and no special file options.</returns>
565        public FileStream Open(FileAccess access, FileShare share)
566        {
567            return Open(access, share, FileOptions.None);
568        }
569
570        /// <summary>
571        /// Opens a file with read, write, or read/write access, the specified
572        /// sharing option, and other advanced options.
573        /// </summary>
574        /// <param name="mode">A System.IO.FileMode constant specifying the mode
575        /// (for example, Open or Append) in which to open the file.</param>
576        /// <param name="access">A System.IO.FileAccess constant specifying whether
577        /// to open the file with Read, Write, or ReadWrite file access.</param>
578        /// <param name="share">A System.IO.FileShare constant specifying the type
579        /// of access other FileStream objects have to this file.</param>
580        /// <param name="options">The System.IO.FileOptions constant specifying
581        /// the advanced file options to use when opening the file.</param>
582        /// <returns>A System.IO.FileStream object opened with the specified mode,
583        /// access, sharing options, and special file options.</returns>
584        public FileStream Open(FileAccess access, FileShare share, FileOptions options)
585        {
586            SafeFileHandle handle = OpenHandle(access, share, options);
587
588            //Check that the handle is valid
589            if (handle.IsInvalid)
590                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
591
592            //Return the FileStream
593            return new FileStream(handle, access);
594        }
595
596        private SafeFileHandle OpenHandle(FileAccess access, FileShare share, FileOptions options)
597        {
598            //Access mode
599            uint iAccess = 0;
600            switch (access)
601            {
602                case FileAccess.Read:
603                    iAccess = KernelApi.NativeMethods.GENERIC_READ;
604                    break;
605                case FileAccess.ReadWrite:
606                    iAccess = KernelApi.NativeMethods.GENERIC_READ |
607                        KernelApi.NativeMethods.GENERIC_WRITE;
608                    break;
609                case FileAccess.Write:
610                    iAccess = KernelApi.NativeMethods.GENERIC_WRITE;
611                    break;
612            }
613
614            //Sharing mode
615            if ((share & FileShare.Inheritable) != 0)
616                throw new NotSupportedException("Inheritable handles are not supported.");
617
618            //Advanced options
619            if ((options & FileOptions.Asynchronous) != 0)
620                throw new NotSupportedException("Asynchronous handles are not implemented.");
621
622            //Create the handle
623            string openPath = VolumeId;
624            if (openPath.Length > 0 && openPath[openPath.Length - 1] == '\\')
625                openPath = openPath.Remove(openPath.Length - 1);
626            SafeFileHandle result = KernelApi.NativeMethods.CreateFile(openPath, iAccess,
627                (uint)share, IntPtr.Zero, (uint)FileMode.Open, (uint)options, IntPtr.Zero);
628            if (result.IsInvalid)
629                throw KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
630
631            return result;
632        }
633
634        public VolumeLock LockVolume(FileStream stream)
635        {
636            return new VolumeLock(stream);
637        }
638    }
639
640    public class VolumeLock : IDisposable
641    {
642        internal VolumeLock(FileStream stream)
643        {
644            uint result = 0;
645            for (int i = 0; !KernelApi.NativeMethods.DeviceIoControl(stream.SafeFileHandle,
646                    KernelApi.NativeMethods.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
647                    0, out result, IntPtr.Zero); ++i)
648            {
649                if (i > 100)
650                    throw new IOException("Could not lock volume.",
651                        KernelApi.GetExceptionForWin32Error(Marshal.GetLastWin32Error()));
652                System.Threading.Thread.Sleep(100);
653            }
654
655            Stream = stream;
656        }
657
658        ~VolumeLock()
659        {
660            Dispose(false);
661        }
662
663        public void Dispose()
664        {
665            Dispose(true);
666        }
667
668        void Dispose(bool disposing)
669        {
670            if (disposing)
671                GC.SuppressFinalize(this);
672
673            //Flush the contents of the buffer to disk since after we unlock the volume
674            //we can no longer write to the volume.
675            Stream.Flush();
676
677            uint result = 0;
678            if (!KernelApi.NativeMethods.DeviceIoControl(Stream.SafeFileHandle,
679                KernelApi.NativeMethods.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero,
680                0, out result, IntPtr.Zero))
681            {
682                throw new IOException("Could not unlock volume.");
683            }
684        }
685
686        private FileStream Stream;
687    }
688}
Note: See TracBrowser for help on using the repository browser.