c# - FSCTL_LOCK_VOLUME 和 WriteFile 失败
问题描述
我创建了一个小应用程序来备份磁盘并从备份中恢复磁盘。逻辑很简单:
- 打电话
CreateFile
_\\.\PhysicalDrive<index>
- 对目标物理驱动器执行相同操作
DeviceIoControl
使用FSCTL_LOCK_VOLUME
源 + 目标句柄调用DeviceIoControl
使用FSCTL_DISMOUNT_VOLUME
源 + 目标句柄调用- 在一个循环中,使用
ReadFile
从源读取并将其写入目标,WriteFile
直到所有字节都被写入。 - 在源句柄和目标句柄
DeviceIoControl
上调用FSCTL_UNLOCK_VOLUME
- 调用
CloseHandle
源句柄和目标句柄
在继续下一次通话之前,我总是检查所有操作是否成功。这非常有效,但我注意到一些令人费解的事情:
如果在进行复制时,操作系统试图对物理磁盘(我知道它被锁定)做一些事情,那么下一个WriteFile
失败。
导致此失败的示例:
- 移除单独的磁盘(例如移除既不用作源也不用作目标的 U 盘)
- 打开磁盘管理
- 在 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)"
}
}
}
解决方案
推荐阅读
- python - 为什么 decimal.getcontext().prec=3 不适用于 decimal.Decimal(1.234)
- laravel - 试图在laravel中获取darksky api数据
- node.js - 如何在 mongoDB 中输入特定查询,
- python - 为什么即使我有预定义的答案,这仍然会返回“对不起”
- c++ - 问题是我如何确定函数中最小数字的结果,以及如何返回它?
- javascript - iframe resizer 在 iphone safari 浏览器中不起作用
- python-3.x - 如何从其他图像中抓取照片/照片
- bash - 检查“rm -i”上的用户输入?
- java - 如果元素为空,则尝试修改 Java Vector 的 get() 以生成新的 T()
- python - LSTM 实现/过拟合