FSCTL_LOCK_VOLUME和WriteFile失败

时间:2018-05-25 07:54:31

标签: c# powershell winapi pinvoke

我创建了一个小应用程序来备份磁盘并从备份恢复磁盘。逻辑非常简单:

  • CreateFile
  • 上致电\\.\PhysicalDrive<index>
  • 对目标物理驱动器执行相同操作
  • 在源+目标句柄<{li>上使用DeviceIoControl呼叫FSCTL_LOCK_VOLUME
  • 在源+目标句柄<{li>上使用DeviceIoControl呼叫FSCTL_DISMOUNT_VOLUME
  • 在循环中,使用ReadFile从源读取并使用WriteFile将其写入目标,直到写入所有字节为止。
  • 在源和目标句柄上使用DeviceIoControl呼叫FSCTL_UNLOCK_VOLUME
  • 在源句柄和目标句柄上调用CloseHandle

在继续下一次通话之前,我总是检查所有操作是否成功。这非常有效,但我注意到有些令人费解的事情:

如果在复制过程中,操作系统尝试对物理磁盘执行某些操作(我知道已锁定),则下一个WriteFile将失败。

导致此失败的事例:

  • 删除单独的磁盘(例如删除未用作源或目标的USB记忆棒)
  • 打开磁盘管理
  • 在PowerShell中,执行Get-Disk

这些事情都导致下一次WriteFile调用失败,我不明白为什么。根据我阅读的文档FSCTL_LOCK_VOLUME应该阻止任何其他进程获取磁盘句柄,因此我无法理解为什么这些东西可能会导致锁定卷上的问题。任何人都可以详细说明并提供解决方法吗?

这是我的代码:它是用PowerShell编写的,但它很容易转换为C#或类似的东西。

function Start-RawCopy
{
    [CmdletBinding()]

    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='FileAndFile')]
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='FileAndDisk')]
        [System.IO.FileInfo]
        $SourceFile,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndFile')]
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndDisk')]
        [UInt16]
        $SourceDiskNumber,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='FileAndFile')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndFile')]
        [System.IO.FileInfo]
        $DestinationFile,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='FileAndDisk')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndDisk')]
        [UInt16]
        $DestinationDiskNumber
    )

    Begin
    {
        $FSCTL_LOCK_VOLUME = 0x00090018
        $FSCTL_UNLOCK_VOLUME = 0x0009001c
        $FSCTL_DISMOUNT_VOLUME = 0x00090020

        $DELETE = 0x00010000
        $READ_CONTROL = 0x00020000
        $WRITE_DAC = 0x00040000
        $WRITE_OWNER = 0x00080000
        $SYNCHRONIZE = 0x00100000
        $STANDARD_RIGHTS_REQUIRED = 0x000F0000
        $STANDARD_RIGHTS_READ = $READ_CONTROL
        $STANDARD_RIGHTS_WRITE = $READ_CONTROL
        $STANDARD_RIGHTS_EXECUTE = $READ_CONTROL
        $FILE_READ_DATA = 0x0001  # file & pipe
        $FILE_LIST_DIRECTORY = 0x0001  # directory
        $FILE_WRITE_DATA = 0x0002  # file & pipe
        $FILE_ADD_FILE = 0x0002  # directory
        $FILE_APPEND_DATA = 0x0004  # file
        $FILE_ADD_SUBDIRECTORY = 0x0004  # directory
        $FILE_CREATE_PIPE_INSTANCE = 0x0004  # named pipe
        $FILE_READ_EA = 0x0008  # file & directory
        $FILE_WRITE_EA = 0x0010  # file & directory
        $FILE_EXECUTE = 0x0020  # file
        $FILE_TRAVERSE = 0x0020  # directory
        $FILE_DELETE_CHILD = 0x0040  # directory
        $FILE_READ_ATTRIBUTES = 0x0080  # all
        $FILE_WRITE_ATTRIBUTES = 0x0100  # all

        $FILE_ALL_ACCESS = $STANDARD_RIGHTS_REQUIRED -bor $SYNCHRONIZE -bor 0x1FF
        $FILE_GENERIC_READ = $STANDARD_RIGHTS_READ -bor $FILE_READ_DATA -bor $FILE_READ_ATTRIBUTES -bor $FILE_READ_EA -bor $SYNCHRONIZE
        $FILE_GENERIC_WRITE = $STANDARD_RIGHTS_WRITE -bor $FILE_WRITE_DATA -bor $FILE_WRITE_ATTRIBUTES -bor $FILE_WRITE_EA -bor $FILE_APPEND_DATA -bor $SYNCHRONIZE
        $FILE_GENERIC_EXECUTE = $STANDARD_RIGHTS_EXECUTE -bor $FILE_READ_ATTRIBUTES -bor $FILE_EXECUTE -bor $SYNCHRONIZE

        $FILE_SHARE_DELETE = 0x00000004
        $FILE_SHARE_READ = 0x00000001
        $FILE_SHARE_WRITE = 0x00000002

        $CREATE_NEW = 1
        $CREATE_ALWAYS = 2
        $OPEN_EXISTING = 3
        $OPEN_ALWAYS = 4
        $TRUNCATE_EXISTING = 5

        Add-Type -AssemblyName System.Core
    }

    Process
    {
        # Validate parameters        
        if ($PSBoundParameters.ContainsKey('SourceFile'))
        {
            if (!$SourceFile.Exists)
            {
                throw "Source file does not exist, cannot continue"
            }

            $source = $SourceFile.FullName
        }
        else 
        {
            $source = "\\.\PhysicalDrive$SourceDiskNumber"
        }

        if ($PSBoundParameters.ContainsKey('DestinationFile'))
        {
            $destination = $DestinationFile.FullName
            $creationDisposition = $CREATE_ALWAYS
        }
        else 
        {
            $destination = "\\.\PhysicalDrive$DestinationDiskNumber"
            $creationDisposition = $OPEN_EXISTING
        }

        try 
        {
            # Start process
            Write-Warning "Starting raw copy - $(Get-Date)"

            # Open source object
            $handleSource = [NativeMethods]::CreateFile($source, $FILE_GENERIC_READ, $FILE_SHARE_READ -bor $FILE_SHARE_WRITE, 0, $OPEN_EXISTING, 0, [IntPtr]::Zero)

            if (!$handleSource)
            {
                throw "Failed to open source object to read"
            }

            # Clean destination volume
            if ($PSBoundParameters.ContainsKey('DestinationDiskNumber'))
            {
                Get-Disk -Number $DestinationDiskNumber | Get-Partition | Remove-Partition -Confirm:$false
                # Without a sleep this will fail on the 5th WriteFile call all the time. Some caching issue?
                Start-Sleep -Seconds 5
            }

            # Open destination object
            $handleDestination = [NativeMethods]::CreateFile($destination, $FILE_ALL_ACCESS, $FILE_SHARE_READ -bor $FILE_SHARE_WRITE -bor $FILE_SHARE_DELETE, 0, $creationDisposition, 0, [IntPtr]::Zero)

            if (!$handleDestination)
            {
                throw "Failed to open destination object to write"
            }

            $i = 0

            # Lock and dismount source volume
            if ($PSBoundParameters.ContainsKey('SourceDiskNumber'))
            {
                if (![NativeMethods]::DeviceIoControl($handleSource, $FSCTL_LOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
                {
                    throw "Failed to lock source volume"
                }

                if (![NativeMethods]::DeviceIoControl($handleSource, $FSCTL_DISMOUNT_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
                {
                    throw "Failed to dismount source volume"
                }
            }

            # Lock and dismount destination volume
            if ($PSBoundParameters.ContainsKey('DestinationDiskNumber'))
            {
                if (![NativeMethods]::DeviceIoControl($handleDestination, $FSCTL_LOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
                {
                    throw "Failed to lock destination volume"
                }

                if (![NativeMethods]::DeviceIoControl($handleDestination, $FSCTL_DISMOUNT_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
                {
                    throw "Failed to dismount destination volume"
                }
            }

            # Get source size
            if ($PSBoundParameters.ContainsKey('SourceDiskNumber'))
            {
                $sourceSize = (Get-CimInstance -Namespace ROOT/Microsoft/Windows/Storage -Class MSFT_Disk -Filter "Number = $SourceDiskNumber").Size
            }
            else
            {
                $sourceSize = $SourceFile.Length
            }

            $buffer = [Byte[]]::new(32MB)
            $bytesRead = [UInt32] 0
            $bytesWritten = [UInt32] 0
            [UInt64] $totalBytesRead = 0

            # Copy data
            do
            {
                if ($totalBytesRead + $buffer.Length -gt $sourceSize)
                {
                    $buffer = [Byte[]]::new($sourceSize - $totalBytesRead)

                    if ($Validate)
                    {
                        $verifyBuffer = [Byte[]]::new($buffer.Length)
                    }
                }

                if (![NativeMethods]::ReadFile($handleSource, $buffer, $buffer.Length, [ref] $bytesRead, [IntPtr]::Zero))
                {
                    throw "Failed to read from source"
                }

                if (![NativeMethods]::WriteFile($handleDestination, $buffer, $bytesRead, [ref] $bytesWritten, [IntPtr]::Zero))
                {
                    throw "Failed to write to destination"
                }

                if ($bytesRead -ne $bytesWritten)
                {
                    throw "Read $bytesRead bytes but only wrote $bytesWritten bytes"
                }

                $totalBytesRead += $bytesRead                
            } until ($totalBytesRead -eq $sourceSize)
        }
        finally
        {
            # Unlock source volume
            if ($PSBoundParameters.ContainsKey('SourceDiskNumber'))
            {
                if ($handleSource -and ![NativeMethods]::DeviceIoControl($handleSource, $FSCTL_UNLOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
                {
                    throw "Failed to unlock source volume"
                }
            }

            # Unlock destination volume
            if ($PSBoundParameters.ContainsKey('DestinationDiskNumber'))
            {
                if ($handleDestination -and ![NativeMethods]::DeviceIoControl($handleDestination, $FSCTL_UNLOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
                {
                    throw "Failed to unlock destination volume"
                }
            }

            # Close source handle
            if ($handleSource -and ![NativeMethods]::CloseHandle($handleSource))
            {
                throw "Failed to close source object"
            }

            # Close destination handle
            if ($handleDestination -and ![NativeMethods]::CloseHandle($handleDestination))
            {
                throw "Failed to close destination object"
            }
            Write-Warning "Ended raw copy - $(Get-Date)"
        }

    }
}

0 个答案:

没有答案