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

Revision 2562, 23.2 KB checked in by EraserSlave, 8 weeks ago (diff)

Backport from Eraser trunk: Better reparse point handling for Eraser 6.0 (https://eraser.heidi.ie/forum/viewtopic.php?f=2&t=8684&p=25969#p25966)
r2561: Part 2 fix for https://eraser.heidi.ie/forum/viewtopic.php?f=2&t=8684&p=25969#p25966 since erasing the actual reparse point would cause Eraser to crash if the reparse point references an invalid target.
r2560: Supplements r2549: Resolve reparse points only for as long as the reparse point we are referring to exists; otherwise fall back to volume name resolution.
r2559: We should only check for FileSystemInfos? in the directory being erased if the directory is not a symbolic link. Otherwise, the check is meaningless.
r2558: When we are setting file times for reparse points, we should fall back to setting the file times for the reparse point itself if we cannot set it on the target of the reparse point (such as if the reparse point points to an invalid object.)

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