我创建了一个小应用程序来备份磁盘并从备份恢复磁盘。逻辑非常简单:
CreateFile
\\.\PhysicalDrive<index>
DeviceIoControl
呼叫FSCTL_LOCK_VOLUME
DeviceIoControl
呼叫FSCTL_DISMOUNT_VOLUME
ReadFile
从源读取并使用WriteFile
将其写入目标,直到写入所有字节为止。DeviceIoControl
呼叫FSCTL_UNLOCK_VOLUME
CloseHandle
在继续下一次通话之前,我总是检查所有操作是否成功。这非常有效,但我注意到有些令人费解的事情:
如果在复制过程中,操作系统尝试对物理磁盘执行某些操作(我知道已锁定),则下一个WriteFile
将失败。
导致此失败的事例:
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)"
}
}
}