2820 lines
78 KiB
C
2820 lines
78 KiB
C
/*++
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
thread.c
|
||
|
||
Abstract:
|
||
|
||
This module is part of the fault tolerance driver for NT. The code
|
||
in this module is related to the thread based processing of the driver.
|
||
This module has code with specific knowledge about the nature of
|
||
mirrors and parity stripes.
|
||
|
||
Author:
|
||
|
||
Bob Rinne (bobri) 2-Mar-1992
|
||
Mike Glass (mglass)
|
||
|
||
Environment:
|
||
|
||
kernel mode only
|
||
|
||
Notes:
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "ntddk.h"
|
||
#include "ftdisk.h"
|
||
|
||
#define COPY_BUFFER_SIZE STRIPE_SIZE
|
||
#define BAD_SECTOR_THRESHHOLD 256
|
||
|
||
#ifdef POOL_TAGGING
|
||
#ifdef ExAllocatePool
|
||
#undef ExAllocatePool
|
||
#endif
|
||
#define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,' TtF')
|
||
#endif
|
||
|
||
|
||
VOID
|
||
FtpMatchInRegistry(
|
||
IN PDEVICE_EXTENSION RootDeviceExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine processes the new FT configuration to find matches between
|
||
the defined sets in the registry and the sets that are already established.
|
||
When a match is found the "mark" is remove. This will leave any sets
|
||
which are to be deleted marked.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the root device extension.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PDEVICE_EXTENSION deviceExtension;
|
||
PDEVICE_EXTENSION partitionExtension;
|
||
PDISK_CONFIG_HEADER registry;
|
||
PDISK_REGISTRY diskRegistry;
|
||
PDISK_DESCRIPTION diskDescription;
|
||
PVOID freePoolAddress;
|
||
ULONG signature;
|
||
USHORT i;
|
||
NTSTATUS status;
|
||
LARGE_INTEGER partitionOffset;
|
||
LARGE_INTEGER partitionSize;
|
||
LARGE_INTEGER regOffset;
|
||
LARGE_INTEGER regSize;
|
||
|
||
status = FtpReturnRegistryInformation(DISK_REGISTRY_VALUE,
|
||
&freePoolAddress,
|
||
(PVOID) ®istry);
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// No registry data.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtpMatchInRegistry: No Value => 0x%x\n",
|
||
status));
|
||
ASSERT(0);
|
||
return;
|
||
}
|
||
|
||
diskRegistry = (PDISK_REGISTRY)
|
||
((PUCHAR)registry + registry->DiskInformationOffset);
|
||
|
||
//
|
||
// Process all disks in the system based on the device object
|
||
// chains created by the driver.
|
||
//
|
||
|
||
deviceExtension = RootDeviceExtension->DiskChain;
|
||
diskDescription = NULL;
|
||
while (deviceExtension) {
|
||
|
||
//
|
||
// Start by searching the registry for a match on this signature.
|
||
//
|
||
|
||
signature = deviceExtension->FtUnion.Identity.Signature;
|
||
diskDescription = &diskRegistry->Disks[0];
|
||
for (i = 0; i < diskRegistry->NumberOfDisks; i++) {
|
||
|
||
if (diskDescription->Signature == signature) {
|
||
|
||
//
|
||
// Located disk description in registry for this disk.
|
||
//
|
||
|
||
break;
|
||
}
|
||
diskDescription = (PDISK_DESCRIPTION)
|
||
&diskDescription->Partitions[diskDescription->NumberOfPartitions];
|
||
}
|
||
|
||
if (i >= diskRegistry->NumberOfDisks) {
|
||
|
||
//
|
||
// Somehow this disk is not described in the registry.
|
||
// Skip the disk.
|
||
//
|
||
|
||
deviceExtension = deviceExtension->DiskChain;
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Walk the partition chain on this disk to locate matches in the registry.
|
||
//
|
||
|
||
partitionExtension = deviceExtension->ProtectChain;
|
||
while (partitionExtension) {
|
||
PDISK_PARTITION registryPartition;
|
||
|
||
for (i = 0; i < diskDescription->NumberOfPartitions; i++) {
|
||
|
||
//
|
||
// Go through the disk description in the registry
|
||
// to match this partition extension.
|
||
//
|
||
|
||
registryPartition = &diskDescription->Partitions[i];
|
||
|
||
partitionOffset = partitionExtension->FtUnion.Identity.PartitionOffset;
|
||
partitionSize = partitionExtension->FtUnion.Identity.PartitionLength;
|
||
regOffset = registryPartition->StartingOffset;
|
||
regSize = registryPartition->Length;
|
||
|
||
if (partitionOffset.QuadPart == regOffset.QuadPart &&
|
||
partitionSize.QuadPart == regSize.QuadPart) {
|
||
|
||
//
|
||
// Found the partition.
|
||
//
|
||
// Make three checks to see if this partition has changed. One is
|
||
// if the partition type in the registry is not the same as it
|
||
// was at the last configuration time. Second, if it is
|
||
// an FT set member, but it is in a new group or has a new member
|
||
// role. This could happen if a set is deleted and a new one
|
||
// of the same type is constructed in the same place. Third
|
||
// is it marked for regeneration. This is a regenerate in place
|
||
// stripe with parity member.
|
||
//
|
||
|
||
if (partitionExtension->Type != registryPartition->FtType) {
|
||
partitionExtension->Flags |= FTF_CONFIGURATION_CHANGED;
|
||
|
||
//
|
||
// If this configuration is changing from a mirror into
|
||
// a normal partition, turn ON the do verify bit in the
|
||
// target object to insure that the file systems will
|
||
// not assume they know anything about the device
|
||
// when a mount request occurs and will be forced
|
||
// to read the disk again.
|
||
//
|
||
|
||
if ((partitionExtension->Type == Mirror) &&
|
||
(partitionExtension->MemberRole == 0) &&
|
||
(registryPartition->FtType == NotAnFtMember)) {
|
||
|
||
FtThreadSetVerifyState(partitionExtension, TRUE);
|
||
}
|
||
}
|
||
|
||
if (partitionExtension->Type != NotAnFtMember) {
|
||
|
||
//
|
||
// Check for a change in group or role.
|
||
//
|
||
|
||
if ((partitionExtension->FtGroup != registryPartition->FtGroup) ||
|
||
(partitionExtension->MemberRole != registryPartition->FtMember)) {
|
||
partitionExtension->Flags |= FTF_CONFIGURATION_CHANGED;
|
||
}
|
||
|
||
//
|
||
// Check for regenerate in place.
|
||
//
|
||
|
||
if (partitionExtension->MemberState == Orphaned) {
|
||
if (registryPartition->FtState == Regenerating) {
|
||
partitionExtension->Flags |= FTF_CONFIGURATION_CHANGED;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// If this is an FT set. Update all members if there has
|
||
// been a change in the membership.
|
||
//
|
||
|
||
if (partitionExtension->Flags & FTF_CONFIGURATION_CHANGED) {
|
||
PDEVICE_EXTENSION memberExtension;
|
||
|
||
memberExtension = partitionExtension->ZeroMember;
|
||
while (memberExtension) {
|
||
memberExtension->Flags |= FTF_CONFIGURATION_CHANGED;
|
||
memberExtension = memberExtension->NextMember;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Look at the next partition in the chain.
|
||
//
|
||
|
||
partitionExtension = partitionExtension->ProtectChain;
|
||
}
|
||
|
||
//
|
||
// Look at the next disk in the chain and force a new search for
|
||
// the disk description in the registry.
|
||
//
|
||
|
||
deviceExtension = deviceExtension->DiskChain;
|
||
}
|
||
|
||
ExFreePool(freePoolAddress);
|
||
}
|
||
|
||
VOID
|
||
FtpUpdateRemainingExtensions(
|
||
IN PDEVICE_EXTENSION RootDeviceExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine processes the device extension chain to locate and "delete"
|
||
sets that are not longer configured. Deletion is a process of removing
|
||
the links that construct the FT set and setting the device extension such
|
||
that it represents a "normal" partition that is not a part of an FT set.
|
||
|
||
If a complete FT set has been deleted the manner by which this routine
|
||
works is to find each member individually during the processing of the disk
|
||
and remove its relationships.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the root device extension.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PDEVICE_EXTENSION deviceExtension;
|
||
PDEVICE_EXTENSION partitionExtension;
|
||
#if 0
|
||
PDEVICE_EXTENSION setExtension;
|
||
PDEVICE_EXTENSION nextExtension;
|
||
#endif
|
||
|
||
deviceExtension = RootDeviceExtension->DiskChain;
|
||
while (deviceExtension) {
|
||
partitionExtension = deviceExtension->ProtectChain;
|
||
while (partitionExtension) {
|
||
|
||
//
|
||
// Check if the configuration changed.
|
||
//
|
||
|
||
if (partitionExtension->Flags & FTF_CONFIGURATION_CHANGED) {
|
||
#if 0
|
||
//
|
||
// FUTURE work:
|
||
// Perform set related resource management.
|
||
//
|
||
|
||
switch (partitionExtension->Type) {
|
||
case StripeWithParity:
|
||
|
||
//
|
||
// Stop the restart thread if no stripes with parity.
|
||
// Free the emergency buffers.
|
||
//
|
||
// fall thru
|
||
//
|
||
|
||
case Stripe:
|
||
|
||
//
|
||
// Free RCB zone if there are no stripes or stripes
|
||
// with parity in the system
|
||
//
|
||
// fall thru
|
||
//
|
||
|
||
case Mirror:
|
||
|
||
//
|
||
// Stop the recovery thread if no stripes with parity
|
||
// mirrors.
|
||
//
|
||
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
#endif
|
||
//
|
||
// This partition changed and was not reconfigured.
|
||
// Insure that it is a "normal" partition and not an
|
||
// FT set member.
|
||
//
|
||
|
||
partitionExtension->Type = NotAnFtMember;
|
||
partitionExtension->NextMember = NULL;
|
||
partitionExtension->ZeroMember = NULL;
|
||
partitionExtension->MemberRole = 0;
|
||
partitionExtension->FtGroup = (USHORT) -1;
|
||
partitionExtension->Flags &= ~(FTF_CONFIGURATION_CHANGED);
|
||
}
|
||
|
||
//
|
||
// Move to the next partition.
|
||
//
|
||
|
||
partitionExtension = partitionExtension->ProtectChain;
|
||
}
|
||
|
||
//
|
||
// Move to the next disk.
|
||
//
|
||
|
||
deviceExtension = deviceExtension->DiskChain;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FtThreadRestartQueuedIrps(
|
||
PDEVICE_EXTENSION DeviceExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
|
||
{
|
||
PFT_REGENERATE_REGION regenerateRegion;
|
||
FT_REGENERATE_LOCATION locale;
|
||
PIO_STACK_LOCATION ftIrpStack;
|
||
PIRP writeIrp;
|
||
KIRQL irql;
|
||
|
||
regenerateRegion = DeviceExtension->RegenerateRegionForGroup;
|
||
|
||
while (1) {
|
||
|
||
ThreadDequeueIrp(regenerateRegion, writeIrp);
|
||
|
||
if (writeIrp == NULL) {
|
||
break;
|
||
}
|
||
|
||
AcquireRegenerateRegionCheck(DeviceExtension, irql);
|
||
|
||
//
|
||
// if the Irp is in the current lock region, just skip this
|
||
// work for now and get it the next time.
|
||
//
|
||
|
||
if (locale = FtpRelationToRegenerateRegion(DeviceExtension, writeIrp)
|
||
== InRegenerateRegion) {
|
||
|
||
QueueIrpToThread(DeviceExtension, writeIrp);
|
||
ReleaseRegenerateRegionCheck(DeviceExtension, irql);
|
||
DebugPrint((1, "FtThreadRestartQueuedIrps: queued irp is in region.\n"));
|
||
break;
|
||
}
|
||
ReleaseRegenerateRegionCheck(DeviceExtension, irql);
|
||
|
||
ftIrpStack = IoGetNextIrpStackLocation(writeIrp);
|
||
ftIrpStack->FtOrgIrpWaitingRegen = (PVOID) 0;
|
||
|
||
DebugPrint((3,
|
||
"FtThreadRestartQueuedIrps: Restarted Irp %x, DE %x locale %d\n",
|
||
writeIrp,
|
||
DeviceExtension,
|
||
locale));
|
||
|
||
(VOID) FtDiskReadWrite(DeviceExtension->DeviceObject, writeIrp);
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
FtThreadCopy(
|
||
PDEVICE_EXTENSION MainDevice
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine handles initialization/regeneration of a mirror.
|
||
This is used to copy the contents from an existing partition onto a
|
||
newly created mirror partition.
|
||
|
||
To perform the copy, this code uses the device object
|
||
for the entire disk instead of the device object for the actual
|
||
partitions involved. This is done because the device object of the
|
||
source drive could be in use from a file system. By using the whole
|
||
disk device object this code avoids any conflicts with the flags
|
||
for the device object used by the file system and need not worry
|
||
about DO_VERIFY_VOLUME conditions.
|
||
|
||
After completion of the copy this routine will cause the state of
|
||
the mirror partition in the registry to change from the "Regenerating"
|
||
state to the "Heathy" state. By waiting to the end it means that if
|
||
the system fails while the copy is in progress it will restart on the
|
||
next boots. This restarting of regeneration will continue until
|
||
it actually succeeds and the registry update to reflect this fact
|
||
succeeds.
|
||
|
||
Arguments:
|
||
|
||
MainDevice - member zero of the mirror. Also assumed to be the
|
||
primary or the source of the copy.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PFT_REGENERATE_REGION regenerateRegion;
|
||
KIRQL irql;
|
||
LARGE_INTEGER offset;
|
||
LARGE_INTEGER wholeDiskOffset;
|
||
LARGE_INTEGER readOffset;
|
||
LARGE_INTEGER writeOffset;
|
||
LARGE_INTEGER copyLength;
|
||
PDEVICE_EXTENSION originalReadDE;
|
||
PDEVICE_EXTENSION readDE;
|
||
PDEVICE_EXTENSION writeDE;
|
||
IO_STATUS_BLOCK ioStatus;
|
||
NTSTATUS status;
|
||
KEVENT firstEvent;
|
||
KEVENT secondEvent;
|
||
PIRP readIrp;
|
||
PIRP writeIrp;
|
||
PUCHAR buffer;
|
||
FT_READ_POLICY originalReadPolicy;
|
||
BOOLEAN initSucceeded = TRUE;
|
||
ULONG bufferSize = COPY_BUFFER_SIZE;
|
||
ULONG nastySectors = 0;
|
||
LARGE_INTEGER delayTime;
|
||
|
||
delayTime.QuadPart = -500000;
|
||
|
||
DebugPrint((1, "FtThreadCopy:\n"));
|
||
|
||
//
|
||
// Set up the read and write device extensions.
|
||
//
|
||
|
||
readDE = MainDevice;
|
||
writeDE = MainDevice->NextMember;
|
||
|
||
//
|
||
// Save the original read policy for the mirror and set the new
|
||
// read policy to only use the primary.
|
||
//
|
||
|
||
originalReadPolicy = readDE->ReadPolicy;
|
||
readDE->ReadPolicy = ReadPrimary;
|
||
|
||
//
|
||
// Allocate a memory block of the maximum transfer size
|
||
// for the two devices.
|
||
//
|
||
|
||
buffer = FtThreadAllocateBuffer(&bufferSize, FALSE);
|
||
|
||
//
|
||
// Initialize length and offset for the copy.
|
||
//
|
||
|
||
copyLength = readDE->FtUnion.Identity.PartitionLength;
|
||
offset.QuadPart = 0;
|
||
|
||
//
|
||
// Get starting offsets for each partition, then move
|
||
// to the device extension for the whole disk. The I/O
|
||
// will occur on this device extension. This is done
|
||
// to avoid any conflicts with the DeviceObject below
|
||
// and file systems (i.e. DO_VERIFY_VOLUME).
|
||
//
|
||
|
||
readOffset = readDE->FtUnion.Identity.PartitionOffset;
|
||
regenerateRegion = readDE->RegenerateRegionForGroup;
|
||
originalReadDE = readDE;
|
||
readDE = readDE->WholeDisk;
|
||
|
||
writeOffset = writeDE->FtUnion.Identity.PartitionOffset;
|
||
ASSERT(regenerateRegion == writeDE->RegenerateRegionForGroup);
|
||
writeDE = writeDE->WholeDisk;
|
||
|
||
|
||
DebugPrint((3,
|
||
"FtThreadCopy: Copy DE %x (%x) offset %x:%x" // no comma
|
||
" to DE %x offset %x:%x for %x:%x\n",
|
||
readDE,
|
||
regenerateRegion,
|
||
readOffset.HighPart,
|
||
readOffset.LowPart,
|
||
writeDE,
|
||
writeOffset.HighPart,
|
||
writeOffset.LowPart,
|
||
copyLength.HighPart,
|
||
copyLength.LowPart));
|
||
|
||
//
|
||
// Check to see if already performing regeneration.
|
||
//
|
||
|
||
AcquireRegenerateRegionCheck(originalReadDE, irql);
|
||
if (regenerateRegion->Active == TRUE) {
|
||
|
||
//
|
||
// Already performing a regenerate. Just exit this one.
|
||
//
|
||
|
||
ReleaseRegenerateRegionCheck(originalReadDE, irql);
|
||
ExFreePool(buffer);
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Set the set state to reflect the fact that a copy is active.
|
||
//
|
||
|
||
MainDevice->VolumeState = FtInitializing;
|
||
|
||
//
|
||
// Set up the lock region and start the copy.
|
||
//
|
||
|
||
regenerateRegion->RowNumber = 0;
|
||
regenerateRegion->Active = TRUE;
|
||
ReleaseRegenerateRegionCheck(originalReadDE, irql);
|
||
|
||
FtpLogError(MainDevice,
|
||
FT_MIRROR_COPY_STARTED,
|
||
0,
|
||
0,
|
||
NULL);
|
||
|
||
while (offset.QuadPart < copyLength.QuadPart) {
|
||
|
||
ULONG size = bufferSize;
|
||
|
||
//
|
||
// Initialize events for this pass.
|
||
//
|
||
|
||
KeInitializeEvent(&firstEvent, NotificationEvent, FALSE);
|
||
KeInitializeEvent(&secondEvent, NotificationEvent, FALSE);
|
||
|
||
//
|
||
// If the current offset plus the size of this I/O is
|
||
// less than the size remaining, then adjust size.
|
||
//
|
||
|
||
if (offset.QuadPart + size > copyLength.QuadPart) {
|
||
|
||
//
|
||
// Less than buffer size remaining.
|
||
//
|
||
|
||
size = (ULONG) (copyLength.QuadPart - offset.QuadPart);
|
||
}
|
||
|
||
DebugPrint((3,
|
||
"FtThreadCopy: Copy offset 0x%x:%x, size 0x%x\n",
|
||
offset.HighPart,
|
||
offset.LowPart,
|
||
size));
|
||
|
||
//
|
||
// Read the data from the source device.
|
||
//
|
||
|
||
wholeDiskOffset.QuadPart = offset.QuadPart + readOffset.QuadPart;
|
||
|
||
DebugPrint((4,
|
||
"FtThreadCopy: Read offset == %x:%x\n",
|
||
wholeDiskOffset.HighPart,
|
||
wholeDiskOffset.LowPart));
|
||
|
||
readAgain:
|
||
readIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,
|
||
readDE->TargetObject,
|
||
buffer,
|
||
size,
|
||
&wholeDiskOffset,
|
||
&firstEvent,
|
||
&ioStatus);
|
||
status = IoCallDriver(readDE->TargetObject, readIrp);
|
||
|
||
if (status == STATUS_PENDING) {
|
||
(VOID) KeWaitForSingleObject(&firstEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER) NULL);
|
||
status = ioStatus.Status;
|
||
}
|
||
|
||
if (status == STATUS_DEVICE_NOT_READY) {
|
||
KeDelayExecutionThread(KernelMode,
|
||
FALSE,
|
||
&delayTime);
|
||
goto readAgain;
|
||
}
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
LARGE_INTEGER partitionOffset = readOffset;
|
||
ULONG failingOffset = 0;
|
||
ULONG sectorSize =
|
||
originalReadDE->FtUnion.Identity.DiskGeometry.BytesPerSector;
|
||
|
||
//
|
||
// This is a read failure. Read as much of the area as possible
|
||
// and copy it to the other side. Trust that the file system
|
||
// has already mapped these sectors from use during the verify
|
||
// pass.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtThreadCopy: Read failure: Status=0x%x,\n\t"
|
||
"readDE=0x%x readIrp=0x%x, buffer=0x%x\n",
|
||
status,
|
||
readDE,
|
||
readIrp,
|
||
buffer));
|
||
|
||
while (!NT_SUCCESS(status)) {
|
||
|
||
if (FsRtlIsTotalDeviceFailure(status)) {
|
||
|
||
//
|
||
// Primary just went bad, disable the set and get out.
|
||
//
|
||
|
||
originalReadDE->VolumeState = FtDisabled;
|
||
DeactivateRegenerateRegion(regenerateRegion);
|
||
FtpLogError(originalReadDE,
|
||
FT_SET_DISABLED,
|
||
STATUS_SUCCESS,
|
||
(ULONG) IO_ERR_DRIVER_ERROR,
|
||
NULL);
|
||
initSucceeded = FALSE;
|
||
goto CleanUp;
|
||
}
|
||
|
||
if (failingOffset >= size) {
|
||
|
||
//
|
||
// Complete area has been walked.
|
||
//
|
||
|
||
break;
|
||
}
|
||
status = FtThreadFindFailingSector((UCHAR)IRP_MJ_READ,
|
||
originalReadDE,
|
||
(PVOID)buffer,
|
||
&partitionOffset,
|
||
size,
|
||
&failingOffset);
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
FtpLogError(originalReadDE->NextMember,
|
||
FT_SECTOR_FAILURE,
|
||
status,
|
||
status,
|
||
NULL);
|
||
|
||
//
|
||
// Zero fill the data buffer and step around the
|
||
// bad sector.
|
||
//
|
||
|
||
RtlZeroMemory((buffer + failingOffset), sectorSize);
|
||
failingOffset += sectorSize;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Write the data to the mirror.
|
||
//
|
||
|
||
wholeDiskOffset.QuadPart = offset.QuadPart + writeOffset.QuadPart;
|
||
|
||
DebugPrint((4,
|
||
"FtThreadCopy: Write offset == %x:%x\n",
|
||
wholeDiskOffset.HighPart,
|
||
wholeDiskOffset.LowPart));
|
||
tryAgain:
|
||
writeIrp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE,
|
||
writeDE->TargetObject,
|
||
buffer,
|
||
size,
|
||
&wholeDiskOffset,
|
||
&secondEvent,
|
||
&ioStatus);
|
||
status = IoCallDriver(writeDE->TargetObject,
|
||
writeIrp);
|
||
|
||
if (status == STATUS_PENDING) {
|
||
(VOID) KeWaitForSingleObject(&secondEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER) NULL);
|
||
status = ioStatus.Status;
|
||
}
|
||
|
||
if (status == STATUS_DEVICE_NOT_READY) {
|
||
KeDelayExecutionThread(KernelMode,
|
||
FALSE,
|
||
&delayTime);
|
||
goto tryAgain;
|
||
}
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
LARGE_INTEGER failingOffset;
|
||
ULONG requestOffset;
|
||
|
||
if (FsRtlIsTotalDeviceFailure(status)) {
|
||
|
||
//
|
||
// Primary just went bad, disable the set and get out.
|
||
//
|
||
|
||
originalReadDE->VolumeState = FtHasOrphan;
|
||
originalReadDE->NextMember->MemberState = Orphaned;
|
||
DeactivateRegenerateRegion(regenerateRegion);
|
||
FtpChangeMemberStateInRegistry(originalReadDE->NextMember, Orphaned);
|
||
FtpLogError(originalReadDE->NextMember,
|
||
FT_ORPHANING,
|
||
STATUS_SUCCESS,
|
||
0,
|
||
NULL);
|
||
initSucceeded = FALSE;
|
||
goto CleanUp;
|
||
}
|
||
|
||
//
|
||
// Attempt recovery which includes issuing command
|
||
// to disk to map out bad sector.
|
||
//
|
||
|
||
status = FtThreadFindFailingSector((UCHAR)IRP_MJ_WRITE,
|
||
writeDE,
|
||
buffer,
|
||
&wholeDiskOffset,
|
||
size,
|
||
&requestOffset);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// Calculate offset of nasty sector.
|
||
//
|
||
|
||
failingOffset.QuadPart = wholeDiskOffset.QuadPart +
|
||
requestOffset;
|
||
|
||
//
|
||
// Attempt to map out nasty sector.
|
||
//
|
||
|
||
if (FtThreadMapBadSector(writeDE,
|
||
&failingOffset)) {
|
||
|
||
FtpLogError(originalReadDE->NextMember,
|
||
FT_SECTOR_RECOVERED,
|
||
status,
|
||
status,
|
||
NULL);
|
||
|
||
goto tryAgain;
|
||
}
|
||
|
||
//
|
||
// Check if bad sector threshhold has been reached.
|
||
//
|
||
|
||
if (++nastySectors > BAD_SECTOR_THRESHHOLD) {
|
||
|
||
//
|
||
// Write failures have exceeded the threshhold.
|
||
// Orphan the shadow, log it, and finish up.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtThreadCopy:write failed Status=0x%x,\n\t"
|
||
"writeDE=0x%x,writeIrp=0x%x,buffer=0x%x\n",
|
||
status,
|
||
writeDE,
|
||
writeIrp,
|
||
buffer));
|
||
FtpLogError(originalReadDE->NextMember,
|
||
FT_ORPHANING,
|
||
status,
|
||
status,
|
||
NULL);
|
||
FtpChangeMemberStateInRegistry(originalReadDE->NextMember, Orphaned);
|
||
DeactivateRegenerateRegion(regenerateRegion);
|
||
FtThreadRestartQueuedIrps(originalReadDE);
|
||
initSucceeded = FALSE;
|
||
goto CleanUp;
|
||
|
||
} else {
|
||
|
||
FtpLogError(originalReadDE->NextMember,
|
||
FT_SECTOR_FAILURE,
|
||
status,
|
||
status,
|
||
NULL);
|
||
}
|
||
}
|
||
}
|
||
|
||
offset.QuadPart += size;
|
||
//
|
||
// Move the lock region to the next row.
|
||
// On the last pass this will move the lock past the end of the
|
||
// mirror and allow the loop below to flush any queued irps.
|
||
//
|
||
|
||
LockNextRegion(regenerateRegion);
|
||
|
||
//
|
||
// Pick up any queued Irps and start them over.
|
||
// These will all be write Irps.
|
||
//
|
||
|
||
FtThreadRestartQueuedIrps(originalReadDE);
|
||
}
|
||
|
||
DeactivateRegenerateRegion(regenerateRegion);
|
||
DebugPrint((1, "FtThreadCopy: Copy Complete\n"));
|
||
|
||
//
|
||
// Restore read policy to original state.
|
||
//
|
||
|
||
readDE->ReadPolicy = originalReadPolicy;
|
||
|
||
//
|
||
// Reflect the fact that the mirror has been regenerated.
|
||
// Unless, it was orphaned in the process.
|
||
//
|
||
|
||
if ((!IsMemberAnOrphan(originalReadDE)) &&
|
||
(!IsMemberAnOrphan(originalReadDE->NextMember))) {
|
||
FtpChangeMemberStateInRegistry(originalReadDE->NextMember, Healthy);
|
||
}
|
||
|
||
//
|
||
// If initialization was successful, change the volume state.
|
||
//
|
||
|
||
if (MainDevice->VolumeState == FtInitializing) {
|
||
MainDevice->VolumeState = FtStateOk;
|
||
}
|
||
|
||
CleanUp:
|
||
|
||
if (initSucceeded) {
|
||
FtpLogError(MainDevice,
|
||
FT_MIRROR_COPY_ENDED,
|
||
status,
|
||
status,
|
||
NULL);
|
||
} else {
|
||
FtpLogError(MainDevice,
|
||
FT_MIRROR_COPY_FAILED,
|
||
status,
|
||
status,
|
||
NULL);
|
||
}
|
||
|
||
//
|
||
// Free resources and terminate thread.
|
||
//
|
||
|
||
ExFreePool(buffer);
|
||
|
||
} // FtThreadCopy()
|
||
|
||
|
||
VOID
|
||
FtThreadIoctl(
|
||
IN PFT_SYNC_CONTEXT Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine handles several FT device controls.
|
||
|
||
Arguments:
|
||
|
||
Context - description of device control request.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PDEVICE_EXTENSION deviceExtension = Context->DeviceExtension;
|
||
|
||
switch (Context->IoctlCode) {
|
||
|
||
case FT_INITIALIZE_SET:
|
||
|
||
DebugPrint((1,
|
||
"FtThreadIoctl: IOCTL FT_INITIALIZE called\n"));
|
||
|
||
switch (deviceExtension->Type) {
|
||
|
||
case StripeWithParity:
|
||
|
||
//
|
||
// Allow use of the stripe with parity.
|
||
//
|
||
|
||
deviceExtension->MemberState = Healthy;
|
||
deviceExtension->VolumeState = FtInitializing;
|
||
|
||
StripeSynchronizeParity(deviceExtension,
|
||
&Context->SyncInfo);
|
||
|
||
//
|
||
// Check if initialization was successful.
|
||
//
|
||
|
||
if (deviceExtension->Flags & FTF_SYNCHRONIZATION_FAILED) {
|
||
|
||
//
|
||
// Since WINDISK only looks at member states, in order
|
||
// to convince it that initialization failed the zero
|
||
// member state must be FtInitializing and another
|
||
// member state must be orphaned. Check here which member
|
||
// is orphaned.
|
||
//
|
||
|
||
if (deviceExtension->MemberState == Orphaned) {
|
||
FtpChangeMemberStateInRegistry(deviceExtension,
|
||
Initializing);
|
||
FtpChangeMemberStateInRegistry(deviceExtension->NextMember,
|
||
Orphaned);
|
||
}
|
||
|
||
//
|
||
// Set the local volume state to FtDisabled to repel
|
||
// IO on this volume.
|
||
|
||
deviceExtension->VolumeState = FtDisabled;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Change zero member state in registry to healthy.
|
||
//
|
||
|
||
FtpChangeMemberStateInRegistry(deviceExtension, Healthy);
|
||
}
|
||
|
||
break;
|
||
|
||
case Mirror:
|
||
|
||
//
|
||
// Mirror always does full volume synchronization (for now).
|
||
//
|
||
|
||
if (deviceExtension->IgnoreReadPolicy == FALSE) {
|
||
FtThreadCopy(deviceExtension);
|
||
}
|
||
|
||
break;
|
||
|
||
} // end switch
|
||
|
||
break;
|
||
|
||
case FT_SYNC_REDUNDANT_COPY:
|
||
|
||
DebugPrint((1,
|
||
"FtThreadIoctl: IOCTL FT_SYNC_REDUNDANT_COPY called\n"));
|
||
|
||
switch (deviceExtension->Type) {
|
||
|
||
case Mirror:
|
||
|
||
//
|
||
// Mirror always does full volume synchronization (for now).
|
||
//
|
||
|
||
if (deviceExtension->IgnoreReadPolicy == FALSE) {
|
||
FtThreadCopy(deviceExtension);
|
||
}
|
||
|
||
break;
|
||
|
||
case StripeWithParity:
|
||
|
||
StripeSynchronizeParity(deviceExtension,
|
||
&Context->SyncInfo);
|
||
|
||
break;
|
||
|
||
} // end switch
|
||
|
||
break;
|
||
|
||
case FT_REGENERATE:
|
||
|
||
DebugPrint((1,
|
||
"FtThreadIoctl: IOCTL FT_REGENERATE called\n"));
|
||
|
||
switch (deviceExtension->Type) {
|
||
|
||
case Mirror:
|
||
|
||
//
|
||
// Mirror always does full volume synchronization (for now).
|
||
//
|
||
|
||
if (deviceExtension->IgnoreReadPolicy == FALSE) {
|
||
FtThreadCopy(deviceExtension);
|
||
}
|
||
|
||
break;
|
||
|
||
case StripeWithParity:
|
||
|
||
//
|
||
// Replace orphaned member of SWP set.
|
||
//
|
||
|
||
StripeRegenerateParity(deviceExtension);
|
||
|
||
//
|
||
// Check if initialization was successful.
|
||
//
|
||
|
||
if (deviceExtension->Flags & FTF_SYNCHRONIZATION_FAILED) {
|
||
|
||
deviceExtension->VolumeState = FtHasOrphan;
|
||
|
||
} else {
|
||
|
||
deviceExtension->VolumeState = FtStateOk;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
break;
|
||
|
||
case FT_CONFIGURE:
|
||
|
||
DebugPrint((1,
|
||
"FtThreadIoctl: IOCTL FT_CONFIGURE called\n"));
|
||
|
||
//
|
||
// Find existing sets in the registry.
|
||
//
|
||
|
||
FtpMatchInRegistry(deviceExtension);
|
||
|
||
//
|
||
// Call routine to do maintenance configuration and create new sets.
|
||
//
|
||
|
||
FtpConfigure(deviceExtension,
|
||
TRUE);
|
||
|
||
//
|
||
// Do maintainance on any extensions that still show configuration
|
||
// flags. This includes deleting old set relationships, or cleaning
|
||
// up member extensions that were replaced due to regen or re-mirror
|
||
// operations.
|
||
//
|
||
|
||
FtpUpdateRemainingExtensions(deviceExtension);
|
||
break;
|
||
|
||
default:
|
||
|
||
DebugPrint((1,
|
||
"FtThread: Unknown Command (%x)\n",
|
||
Context->IoctlCode));
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Complete the IRP if supplied.
|
||
//
|
||
|
||
if (Context->Irp) {
|
||
IoCompleteRequest(Context->Irp, IO_NO_INCREMENT);
|
||
}
|
||
|
||
//
|
||
// Free context block.
|
||
//
|
||
|
||
if (Context) {
|
||
ExFreePool(Context);
|
||
}
|
||
|
||
return;
|
||
|
||
} // FtThreadIoctl
|
||
|
||
|
||
NTSTATUS
|
||
FtThreadStartNewThread(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN ULONG IoctlCode,
|
||
IN PIRP Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine spawns a thread to handle a specific synchronization task.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - Description of FT set.
|
||
IoctlCode - Device control function.
|
||
Irp - Device control request.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
PFT_SYNC_CONTEXT syncContext;
|
||
HANDLE threadHandle;
|
||
NTSTATUS status, fstatus;
|
||
|
||
//
|
||
// Allocate and copy sync information to memory. This is done
|
||
// as the filesystems may call this device control at DPC level.
|
||
// Note: The actual sync routine will free this allocation.
|
||
//
|
||
|
||
syncContext = ExAllocatePool(NonPagedPool,
|
||
sizeof(FT_SYNC_CONTEXT));
|
||
if (syncContext == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
//
|
||
// Save IRP pointer if this a synchronous call.
|
||
//
|
||
|
||
if (IoctlCode == FT_CONFIGURE) {
|
||
IoMarkIrpPending(Irp);
|
||
syncContext->Irp = Irp;
|
||
fstatus = STATUS_PENDING;
|
||
} else {
|
||
syncContext->Irp = NULL;
|
||
fstatus = STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
//
|
||
// Store device extension in context block.
|
||
//
|
||
|
||
syncContext->DeviceExtension = DeviceExtension;
|
||
|
||
//
|
||
// Save IOCTL code.
|
||
//
|
||
|
||
syncContext->IoctlCode = IoctlCode;
|
||
|
||
//
|
||
// If system buffer specified then use it, otherwise
|
||
// assume request if for synchronization of the entire volume.
|
||
//
|
||
|
||
if (Irp &&
|
||
Irp->AssociatedIrp.SystemBuffer) {
|
||
|
||
//
|
||
// Copy sync information from IRP. The filesystems use this
|
||
// device control at DPC level so the IRP must be returned
|
||
// without blocking.
|
||
//
|
||
|
||
syncContext->SyncInfo =
|
||
*((PFT_SYNC_INFORMATION)Irp->AssociatedIrp.SystemBuffer);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Request is for the complete volume size.
|
||
//
|
||
|
||
syncContext->SyncInfo.ByteOffset.QuadPart = 0;
|
||
FtpVolumeLength(DeviceExtension,
|
||
&syncContext->SyncInfo.ByteCount);
|
||
}
|
||
|
||
//
|
||
// Spawn thread to complete these requests.
|
||
//
|
||
|
||
status = PsCreateSystemThread(&threadHandle,
|
||
(ACCESS_MASK) 0L,
|
||
(POBJECT_ATTRIBUTES) NULL,
|
||
(HANDLE) 0L,
|
||
(PCLIENT_ID) NULL,
|
||
(PKSTART_ROUTINE) FtThreadIoctl,
|
||
(PVOID) syncContext);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
ExFreePool(syncContext);
|
||
fstatus = status;
|
||
} else {
|
||
ZwClose(threadHandle);
|
||
}
|
||
|
||
return fstatus;
|
||
|
||
} // end FtThreadStartNewThread
|
||
|
||
|
||
PUCHAR
|
||
FtThreadAllocateBuffer(
|
||
IN OUT PULONG DesiredSize,
|
||
IN BOOLEAN SizeRequired
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine attempts to allocate a buffer for the thread logic. If
|
||
the desired size cannot be obtained, it is cut in half and another
|
||
attempt is made. This continues until a buffer can be allocated.
|
||
|
||
Arguments:
|
||
|
||
DesiredSize - the asking size. This will change if it cannot be aquired.
|
||
SizeRequired - the caller must have the specified size.
|
||
|
||
Return Value:
|
||
|
||
A pointer to the buffer and the size of the buffer allocated.
|
||
|
||
NOTE:
|
||
Currently loops forever.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG size = *DesiredSize;
|
||
PUCHAR buffer = NULL;
|
||
LARGE_INTEGER delayTime;
|
||
BOOLEAN sleepNow = FALSE;
|
||
|
||
delayTime.QuadPart = -(BUFFER_DELAY);
|
||
|
||
while (1) {
|
||
buffer = (PUCHAR) ExAllocatePool(NonPagedPoolCacheAligned,
|
||
size);
|
||
if (buffer != NULL) {
|
||
break;
|
||
}
|
||
|
||
if (SizeRequired == TRUE) {
|
||
sleepNow = TRUE;
|
||
} else {
|
||
size = size / 2;
|
||
if (size < PAGE_SIZE) {
|
||
sleepNow = TRUE;
|
||
size = *DesiredSize;
|
||
}
|
||
}
|
||
|
||
if (sleepNow == TRUE) {
|
||
DebugPrint((2,
|
||
"FtThreadAllocateBuffer: NO PAGES FREE, asking for %d!",
|
||
size / PAGE_SIZE));
|
||
KeDelayExecutionThread(KernelMode,
|
||
FALSE,
|
||
&delayTime);
|
||
sleepNow = FALSE;
|
||
}
|
||
}
|
||
|
||
*DesiredSize = size;
|
||
return buffer;
|
||
}
|
||
|
||
|
||
VOID
|
||
FtThreadVerifyMirror(
|
||
PDEVICE_EXTENSION MainDevice
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine spins in the background (i.e. after completing the
|
||
requesting Irp) and compares the two sides of a mirror. It is
|
||
largely intended for debug and varification of the mirror components.
|
||
This code uses the device objects of the entire disk (partition0)
|
||
for all I/O operations to avoid conflicts with DO_VERIFY_VOLUME
|
||
flags. This is because the mirror may be in use by a file system
|
||
during verification.
|
||
|
||
|
||
Arguments:
|
||
|
||
MainDevice - member zero of the mirror to verify.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
LARGE_INTEGER offset;
|
||
LARGE_INTEGER wholeDiskOffset;
|
||
LARGE_INTEGER offsetFirst;
|
||
LARGE_INTEGER offsetSecond;
|
||
LARGE_INTEGER verifyLength;
|
||
PDEVICE_EXTENSION extensionFirst;
|
||
PDEVICE_EXTENSION extensionSecond;
|
||
PUCHAR bufferFirst;
|
||
PUCHAR bufferSecond;
|
||
IO_STATUS_BLOCK ioStatus;
|
||
NTSTATUS status;
|
||
KEVENT firstEvent;
|
||
KEVENT secondEvent;
|
||
PIRP readIrp;
|
||
ULONG bufferSize = COPY_BUFFER_SIZE;
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyMirror: DE = %x, Next = %x," // no comma
|
||
" Type = %x, Group = %x, Member = %x\n",
|
||
MainDevice,
|
||
MainDevice->NextMember,
|
||
MainDevice->Type,
|
||
MainDevice->FtGroup,
|
||
MainDevice->MemberRole));
|
||
|
||
extensionFirst = MainDevice;
|
||
extensionSecond = MainDevice->NextMember;
|
||
|
||
if (extensionSecond == NULL) {
|
||
|
||
//
|
||
// Bad argument. Just exit the thread.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
ASSERT(extensionFirst != NULL);
|
||
ASSERT(extensionSecond != NULL);
|
||
|
||
//
|
||
// Allocate a memory block of the maximum transfer size
|
||
// for the two devices.
|
||
//
|
||
|
||
bufferFirst = FtThreadAllocateBuffer(&bufferSize, FALSE);
|
||
|
||
//
|
||
// Go in and ask for the same size as the first buffer.
|
||
// This means it will be the same size or smaller and the bufferSize
|
||
// will be updated to reflect the smaller of the two buffers.
|
||
//
|
||
|
||
bufferSecond = FtThreadAllocateBuffer(&bufferSize, FALSE);
|
||
|
||
//
|
||
// Initialize length and offset for the verify.
|
||
//
|
||
|
||
verifyLength = extensionFirst->FtUnion.Identity.PartitionLength;
|
||
offset.QuadPart = 0;
|
||
|
||
//
|
||
// Get starting offsets for each partition, then move
|
||
// to the device extension for the whole disk. The I/O
|
||
// will occur on this device extension. This is done
|
||
// to avoid any conflicts with the DeviceObject below
|
||
// and file systems (i.e. DO_VERIFY_VOLUME).
|
||
//
|
||
|
||
offsetFirst = extensionFirst->FtUnion.Identity.PartitionOffset;
|
||
extensionFirst = extensionFirst->WholeDisk;
|
||
offsetSecond = extensionSecond->FtUnion.Identity.PartitionOffset;
|
||
extensionSecond = extensionSecond->WholeDisk;
|
||
|
||
DebugPrint((3,
|
||
"FtThreadVerifyMirror: Verify DE %x offset %x:%x" // no comma
|
||
" to DE %x offset %x:%x for %x:%x\n",
|
||
extensionFirst,
|
||
offsetFirst.HighPart,
|
||
offsetFirst.LowPart,
|
||
extensionSecond,
|
||
offsetSecond.HighPart,
|
||
offsetSecond.LowPart,
|
||
verifyLength.HighPart,
|
||
verifyLength.LowPart));
|
||
|
||
while (offset.QuadPart < verifyLength.QuadPart) {
|
||
|
||
ULONG size = bufferSize;
|
||
ULONG diffLocation;
|
||
|
||
//
|
||
// Initialize the events for this pass.
|
||
//
|
||
|
||
KeInitializeEvent(&firstEvent, NotificationEvent, FALSE);
|
||
KeInitializeEvent(&secondEvent, NotificationEvent, FALSE);
|
||
|
||
//
|
||
// If the current offset plus the size of this I/O is
|
||
// less than the size remaining, then adjust size.
|
||
//
|
||
|
||
if (offset.QuadPart + size > verifyLength.QuadPart) {
|
||
|
||
//
|
||
// Less than buffer size remaining.
|
||
//
|
||
|
||
size = (ULONG) (verifyLength.QuadPart - offset.QuadPart);
|
||
}
|
||
|
||
DebugPrint((3,
|
||
"FtThreadVerifyMirror: Verify offset 0x%x:%x, size 0x%x\n",
|
||
offset.HighPart,
|
||
offset.LowPart,
|
||
size));
|
||
|
||
//
|
||
// Read the data from the source device.
|
||
//
|
||
|
||
wholeDiskOffset.QuadPart = offset.QuadPart + offsetFirst.QuadPart;
|
||
|
||
DebugPrint((4,
|
||
"FtThreadVerifyMirror: Read offset 1 == %x:%x Device = 0x%x\n",
|
||
wholeDiskOffset.HighPart,
|
||
wholeDiskOffset.LowPart,
|
||
extensionFirst->TargetObject));
|
||
|
||
readIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,
|
||
extensionFirst->TargetObject,
|
||
bufferFirst,
|
||
size,
|
||
&wholeDiskOffset,
|
||
&firstEvent,
|
||
&ioStatus);
|
||
status = IoCallDriver(extensionFirst->TargetObject, readIrp);
|
||
|
||
if (status == STATUS_PENDING) {
|
||
(VOID) KeWaitForSingleObject(&firstEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER) NULL);
|
||
status = ioStatus.Status;
|
||
}
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyMirror: 1st Read failure: Status=0x%x,\n\t"
|
||
"DE=0x%x Irp=0x%x, buffer=0x%x\n",
|
||
status,
|
||
extensionFirst,
|
||
readIrp,
|
||
bufferFirst));
|
||
ASSERT(NT_SUCCESS(status));
|
||
}
|
||
|
||
//
|
||
// read second device.
|
||
//
|
||
|
||
wholeDiskOffset.QuadPart = offset.QuadPart + offsetSecond.QuadPart;
|
||
|
||
DebugPrint((4,
|
||
"FtThreadVerifyMirror: Read offset 2 == %x:%x Device = 0x%x\n",
|
||
wholeDiskOffset.HighPart,
|
||
wholeDiskOffset.LowPart,
|
||
extensionSecond->TargetObject));
|
||
readIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,
|
||
extensionSecond->TargetObject,
|
||
bufferSecond,
|
||
size,
|
||
&wholeDiskOffset,
|
||
&secondEvent,
|
||
&ioStatus);
|
||
status = IoCallDriver(extensionSecond->TargetObject,
|
||
readIrp);
|
||
|
||
if (status == STATUS_PENDING) {
|
||
(VOID) KeWaitForSingleObject(&secondEvent,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER) NULL);
|
||
status = ioStatus.Status;
|
||
}
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyMirror:2nd read failed Status=0x%x,\n\t"
|
||
"DE=0x%x,Irp=0x%x,buffer=0x%x\n",
|
||
status,
|
||
extensionSecond,
|
||
readIrp,
|
||
bufferSecond));
|
||
ASSERT(NT_SUCCESS(status));
|
||
}
|
||
|
||
//
|
||
// Have data from both components. Perform the compare.
|
||
//
|
||
|
||
if ((diffLocation = RtlCompareMemory(bufferFirst, bufferSecond, size)) != size) {
|
||
ULONG sameLocation = diffLocation;
|
||
ULONG sameGroup = 0;
|
||
ULONG maxSame = 0;
|
||
BOOLEAN printedLine = FALSE;
|
||
|
||
//
|
||
// There is a difference.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyMirror: DIFF offset %x:%x Location %x" // no ,
|
||
"\n\t\t\t\t\t\t%2x%2x%2x%2x%2x <> %2x%2x%2x%2x%2x\n",
|
||
offset.HighPart,
|
||
offset.LowPart,
|
||
diffLocation,
|
||
bufferFirst[diffLocation],
|
||
bufferFirst[diffLocation + 1],
|
||
bufferFirst[diffLocation + 2],
|
||
bufferFirst[diffLocation + 3],
|
||
bufferFirst[diffLocation + 4],
|
||
bufferSecond[diffLocation],
|
||
bufferSecond[diffLocation + 1],
|
||
bufferSecond[diffLocation + 2],
|
||
bufferSecond[diffLocation + 3],
|
||
bufferSecond[diffLocation + 4]));
|
||
|
||
while (sameLocation < size) {
|
||
if (bufferFirst[sameLocation] == bufferSecond[sameLocation]) {
|
||
sameGroup++;
|
||
if (sameGroup == 512) {
|
||
if (printedLine == FALSE) {
|
||
DebugPrint((0,
|
||
"FtThreadVerifyMirror: same again at offset %x\n",
|
||
sameLocation - 512));
|
||
printedLine = TRUE;
|
||
}
|
||
sameGroup = 0;
|
||
}
|
||
} else {
|
||
if (sameGroup > maxSame) {
|
||
maxSame = sameGroup;
|
||
}
|
||
sameGroup = 0;
|
||
}
|
||
sameLocation++;
|
||
}
|
||
|
||
DebugPrint((0, "FtThreadVerifyMirror: Maximum same == %d\n", maxSame));
|
||
}
|
||
|
||
offset.QuadPart += size;
|
||
}
|
||
DebugPrint((1, "FtThreadVerifyMirror: complete\n"));
|
||
|
||
ExFreePool(bufferFirst);
|
||
ExFreePool(bufferSecond);
|
||
|
||
} // FtThreadVerifyMirror()
|
||
|
||
|
||
FT_REGENERATE_LOCATION
|
||
FtpRelationToRegenerateRegion(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PIRP Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine looks at the location of the I/O request relative to the
|
||
location of the regenerate region on the FT volume and returns an
|
||
indication of this location.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - Member zero device extension for the volume.
|
||
Irp - the I/O request.
|
||
|
||
Return Value:
|
||
|
||
FT_REGENERATE_LOCATION - an enumerated type for "before", "in", and "after"
|
||
|
||
NOTES: Caller must own the spin lock for this volume.
|
||
|
||
--*/
|
||
|
||
{
|
||
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
||
ULONG members;
|
||
ULONG stripeLocation;
|
||
ULONG row;
|
||
|
||
members = DeviceExtension->FtCount.NumberOfMembers - 1;
|
||
stripeLocation = (ULONG) (irpStack->Parameters.Read.ByteOffset.QuadPart >>
|
||
STRIPE_SHIFT);
|
||
row = stripeLocation / members;
|
||
|
||
if (row == DeviceExtension->RegenerateRegionForGroup->RowNumber) {
|
||
return InRegenerateRegion;
|
||
} else if (row > DeviceExtension->RegenerateRegionForGroup->RowNumber) {
|
||
return AfterRegenerateRegion;
|
||
}
|
||
|
||
//
|
||
// checked the start of the I/O, now check the end of the I/O
|
||
//
|
||
|
||
stripeLocation = (ULONG)
|
||
((irpStack->Parameters.Read.ByteOffset.QuadPart +
|
||
irpStack->Parameters.Read.Length) >> STRIPE_SHIFT);
|
||
|
||
row = stripeLocation / members;
|
||
|
||
if (row >= DeviceExtension->RegenerateRegionForGroup->RowNumber) {
|
||
|
||
//
|
||
// Greater than check is in case I/O completely spans the lock
|
||
// region.
|
||
//
|
||
|
||
return InRegenerateRegion;
|
||
}
|
||
|
||
return BeforeRegenerateRegion;
|
||
}
|
||
|
||
|
||
VOID
|
||
FtThreadMirrorRecovery(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PIRP FailingIrp,
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the main entry for the FT common recovery routine. It assumes a
|
||
thread context and processes the failures of I/O requests to sets.
|
||
During the processing of the failing I/O, the information stored in the
|
||
master Irp cannot be written. Only on completion of the recovery process
|
||
when it is determined that there is no other I/O outstanding on the master
|
||
Irp can the status field (or any field) of the master Irp be modified.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the device extension for the set.
|
||
FailingIrp - the i/o request containing a failing sector.
|
||
Context - the original i/o request.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL irql;
|
||
PKSPIN_LOCK spinLock;
|
||
NTSTATUS status = FailingIrp->IoStatus.Status;
|
||
PIO_STACK_LOCATION failingIrpStack =
|
||
IoGetCurrentIrpStackLocation(FailingIrp);
|
||
|
||
//
|
||
// Must initiate recovery. Recovery is performed via the following
|
||
// algorithms:
|
||
//
|
||
// If failure is a complete device failure code then mark the device
|
||
// extension orphaned. If this is a second orphan disabled
|
||
// the volume. If this is a single orphan and the function
|
||
// was write, then disable the member in the registry. Also,
|
||
// if the orphan is the primary and the set is currently in
|
||
// the initialize or regenerate state, disable the set.
|
||
//
|
||
// Otherwise (discussion geared towards mirrors and implemented in
|
||
// routines called from this routine):
|
||
//
|
||
// Read request - Construct and send a read of the same data from the
|
||
// other component of the mirrored pair.
|
||
// SUCCESS - attempt to write the data back to the failing location.
|
||
// SUCCESS - log a low severity error and return the
|
||
// data to the system.
|
||
// Fail - attempt to map the failing sector.
|
||
// SUCCESS - Log a medium severity error and
|
||
// return the data to the system.
|
||
// Fail - Log a high severity error and
|
||
// return data and retry success
|
||
// to the system.
|
||
// Fail - Log a high severity failure and return the error
|
||
// to the system.
|
||
//
|
||
// Write request - Attempt to map the sector.
|
||
// SUCCESS - write the data, log low priority error.
|
||
// Fail - If no failure on other side, log medium priority error.
|
||
// If other side fails, log high priority error and return
|
||
// error to system.
|
||
//
|
||
// This routine is responsible for determining if the error is a device
|
||
// failure or if sector based recovery is necessary.
|
||
//
|
||
|
||
DebugPrint((2,
|
||
"FtThreadMirrorRecovery: IRP %x (%x) on %s - offset %x:%x length %x\n",
|
||
FailingIrp,
|
||
status,
|
||
(failingIrpStack->MajorFunction == IRP_MJ_READ) ? "read" : "write",
|
||
failingIrpStack->Parameters.Read.ByteOffset.HighPart,
|
||
failingIrpStack->Parameters.Read.ByteOffset.LowPart,
|
||
failingIrpStack->Parameters.Read.Length));
|
||
|
||
if (FsRtlIsTotalDeviceFailure(status)) {
|
||
|
||
//
|
||
// VolumeState is only maintained in the primary of the mirror,
|
||
// therefore no check to see if this is the primary extension is
|
||
// necessary. The mirror copy thread will also disable the set
|
||
// if it encounters an error on reading. Therefore test for a
|
||
// disabled set as well.
|
||
//
|
||
|
||
if ((DeviceExtension->VolumeState == FtInitializing) ||
|
||
(DeviceExtension->VolumeState == FtDisabled)) {
|
||
PIRP masterIrp = (PIRP) Context;
|
||
PIO_STACK_LOCATION ftIrpStack = IoGetNextIrpStackLocation(masterIrp);
|
||
|
||
//
|
||
// No recovery can be performed.
|
||
//
|
||
|
||
DeviceExtension->VolumeState = FtDisabled;
|
||
|
||
//
|
||
// Decrement and get the count of remaining IRPS.
|
||
//
|
||
|
||
spinLock = &DeviceExtension->IrpCountSpinLock;
|
||
KeAcquireSpinLock(spinLock, &irql);
|
||
|
||
(ULONG)ftIrpStack->FtOrgIrpCount = (ULONG)ftIrpStack->FtOrgIrpCount - 1;
|
||
if ((ULONG)ftIrpStack->FtOrgIrpCount == 0) {
|
||
|
||
//
|
||
// I/O processing is complete. Return the master IRP.
|
||
//
|
||
|
||
KeReleaseSpinLock(spinLock, irql);
|
||
if (FailingIrp != NULL) {
|
||
masterIrp->IoStatus = FailingIrp->IoStatus;
|
||
FtpFreeIrp(FailingIrp);
|
||
}
|
||
FtpCompleteRequest(masterIrp, IO_DISK_INCREMENT);
|
||
} else {
|
||
|
||
//
|
||
// first Irp is complete. Wait for second.
|
||
//
|
||
|
||
KeReleaseSpinLock(spinLock, irql);
|
||
if (FailingIrp != NULL) {
|
||
FtpFreeIrp(FailingIrp);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
//
|
||
// If this is a write perform the orphan work.
|
||
//
|
||
|
||
if (failingIrpStack->MajorFunction == IRP_MJ_WRITE) {
|
||
|
||
//
|
||
// This is a write request. The mirrors will now be out of
|
||
// sync so the failing member must be orphaned.
|
||
//
|
||
|
||
if (!IsMemberAnOrphan(DeviceExtension)) {
|
||
FtpOrphanMember(DeviceExtension);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Go to the mirror specific code to handle reads and complete writes.
|
||
//
|
||
|
||
MirrorDeviceFailure(DeviceExtension,
|
||
FailingIrp,
|
||
Context);
|
||
} else {
|
||
|
||
MirrorRecoverFailedIo(DeviceExtension,
|
||
FailingIrp,
|
||
Context);
|
||
}
|
||
|
||
return;
|
||
|
||
} // end FtThreadMirrorRecovery
|
||
|
||
|
||
VOID
|
||
FtRecoveryThread(
|
||
IN PDEVICE_EXTENSION FtRootExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the main entry for the FT recovery thread. This routine waits for
|
||
requests to be queued on the device extension queue for the thread
|
||
then processes them.
|
||
|
||
Arguments:
|
||
|
||
FtRootExtension - the device extension root for the FT driver.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY request;
|
||
PIRP irp;
|
||
PRCB rcb;
|
||
PIO_STACK_LOCATION nextIrpStack;
|
||
PDEVICE_EXTENSION deviceExtension;
|
||
|
||
DebugPrint((3, "FtRecoveryThread: Beginning execution\n"));
|
||
|
||
while (TRUE) {
|
||
|
||
//
|
||
// Wait for a request.
|
||
// KeWaitForSingleObject won't return an error here - this thread
|
||
// isn't alertable, won't take APCs, and there is no timeout.
|
||
//
|
||
|
||
KeWaitForSingleObject((PVOID)
|
||
&(FtRootExtension->FtUnion.Thread.Semaphore),
|
||
UserRequest,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER) NULL);
|
||
|
||
while (!IsListEmpty(&(FtRootExtension->FtUnion.Thread.List))) {
|
||
|
||
//
|
||
// Get the request from the queue.
|
||
//
|
||
|
||
request =
|
||
ExInterlockedRemoveHeadList(&FtRootExtension->FtUnion.Thread.List,
|
||
&FtRootExtension->FtUnion.Thread.SpinLock);
|
||
|
||
//
|
||
// Request may be an IRP or an RCB. Check for both.
|
||
//
|
||
|
||
rcb = CONTAINING_RECORD(request, RCB, ListEntry);
|
||
|
||
if (rcb->Type == RCB_TYPE) {
|
||
|
||
irp = NULL;
|
||
deviceExtension = rcb->ZeroExtension;
|
||
} else {
|
||
|
||
irp = CONTAINING_RECORD(request, IRP, Tail.Overlay.ListEntry);
|
||
ASSERT(irp->Type == IO_TYPE_IRP);
|
||
nextIrpStack = IoGetNextIrpStackLocation(irp);
|
||
deviceExtension = (PDEVICE_EXTENSION)nextIrpStack->DeviceObject;
|
||
}
|
||
|
||
//
|
||
// Branch on stripe or mirror.
|
||
//
|
||
|
||
if (deviceExtension->Type == StripeWithParity) {
|
||
|
||
FtThreadStripeRecovery(rcb);
|
||
|
||
} else {
|
||
|
||
FtThreadMirrorRecovery(deviceExtension,
|
||
irp,
|
||
nextIrpStack->Context);
|
||
}
|
||
}
|
||
}
|
||
|
||
DebugPrint((3, "FtRecoveryThread: Exiting\n"));
|
||
|
||
} // FtRecoveryThread
|
||
|
||
|
||
VOID
|
||
FtRestartThread(
|
||
IN PDEVICE_EXTENSION FtRootExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the main entry for the FT restart thread. This routine waits for
|
||
requests to be queued on the device extension queue for the thread
|
||
then processes them.
|
||
|
||
Arguments:
|
||
|
||
FtRootExtension - the device extension root for the FT driver.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY request;
|
||
PRCB rcb;
|
||
|
||
while (TRUE) {
|
||
|
||
//
|
||
// Wait for a request.
|
||
// KeWaitForSingleObject won't return an error here - this thread
|
||
// isn't alertable, won't take APCs, and there is no timeout.
|
||
//
|
||
|
||
KeWaitForSingleObject((PVOID)
|
||
&(FtRootExtension->RestartThread.Semaphore),
|
||
UserRequest,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER)NULL);
|
||
|
||
while (!IsListEmpty(&(FtRootExtension->RestartThread.List))) {
|
||
|
||
//
|
||
// Get the request from the queue.
|
||
//
|
||
|
||
request =
|
||
ExInterlockedRemoveHeadList(&FtRootExtension->RestartThread.List,
|
||
&FtRootExtension->RestartThread.SpinLock);
|
||
|
||
rcb = CONTAINING_RECORD(request, RCB, ListEntry);
|
||
|
||
//
|
||
// Restart request.
|
||
//
|
||
|
||
DebugPrint((2,
|
||
"FtRestartThread: Restart RCB %x\n",
|
||
rcb));
|
||
|
||
rcb->StartRoutine(rcb);
|
||
}
|
||
}
|
||
|
||
} // FtRestartThread()
|
||
|
||
|
||
NTSTATUS
|
||
FtCreateThread(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PFT_THREAD_DESCRIPTION FtThread,
|
||
IN PKSTART_ROUTINE ThreadRoutine
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Create thread to do background work for failure recovery or
|
||
restarting requests.
|
||
|
||
Arguments:
|
||
|
||
FtThread - Communication area for a thread.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
|
||
RtlZeroMemory(FtThread, sizeof(FT_THREAD_DESCRIPTION));
|
||
|
||
//
|
||
// Initialize the thread and thread communications.
|
||
//
|
||
|
||
KeInitializeSemaphore(&FtThread->Semaphore,
|
||
0L,
|
||
MAXLONG);
|
||
|
||
//
|
||
// Allocate spinlock to protect thread.
|
||
//
|
||
|
||
KeInitializeSpinLock(&FtThread->SpinLock);
|
||
InitializeListHead(&FtThread->List);
|
||
|
||
//
|
||
// Initialize synchronization event for I/Os
|
||
//
|
||
|
||
KeInitializeEvent(&FtThread->Event,
|
||
NotificationEvent,
|
||
FALSE);
|
||
//
|
||
// Create a thread.
|
||
//
|
||
|
||
status = PsCreateSystemThread(&FtThread->Handle,
|
||
(ACCESS_MASK) 0L,
|
||
(POBJECT_ATTRIBUTES) NULL,
|
||
(HANDLE) 0L,
|
||
(PCLIENT_ID) NULL,
|
||
ThreadRoutine,
|
||
DeviceExtension);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
ZwClose(FtThread->Handle);
|
||
}
|
||
|
||
return status;
|
||
|
||
} // end FtCreateThread()
|
||
|
||
|
||
NTSTATUS
|
||
FtThreadFindFailingSector(
|
||
IN UCHAR MajorFunction,
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PVOID DataBuffer,
|
||
IN PLARGE_INTEGER ByteOffset,
|
||
IN ULONG ByteCount,
|
||
IN OUT PULONG FailingOffset
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine reads the area of the disk described by the input parameters
|
||
and returns when a failing sector is located.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the failing member device extension.
|
||
MajorFunction - read or write request.
|
||
DataBuffer - address of data buffer.
|
||
ByteOffset - byte offset from beginning of partition.
|
||
ByteCount - number of bytes to check.
|
||
FailingOffset - the location to start the search and the location
|
||
of a failing sector if found.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Assume successful status even if not bytes are remaining.
|
||
//
|
||
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
PUCHAR buffer = (PUCHAR)DataBuffer + *FailingOffset;
|
||
ULONG ioSize =
|
||
DeviceExtension->FtUnion.Identity.DiskGeometry.BytesPerSector;
|
||
ULONG bytesRemaining = ByteCount - *FailingOffset;
|
||
LARGE_INTEGER offset;
|
||
|
||
offset.QuadPart = ByteOffset->QuadPart + *FailingOffset;
|
||
|
||
while (bytesRemaining) {
|
||
|
||
if (!NT_SUCCESS(status = FtThreadReadWriteSectors(MajorFunction,
|
||
DeviceExtension,
|
||
buffer,
|
||
&offset,
|
||
ioSize))) {
|
||
break;
|
||
}
|
||
|
||
buffer += ioSize;
|
||
bytesRemaining -= ioSize;
|
||
offset.QuadPart += ioSize;
|
||
}
|
||
|
||
//
|
||
// Update the location of the failing sector.
|
||
//
|
||
|
||
*FailingOffset = ByteCount - bytesRemaining;
|
||
return status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
FtThreadReadWriteSectors(
|
||
IN UCHAR MajorFunction,
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PVOID Buffer,
|
||
IN PLARGE_INTEGER Offset,
|
||
IN ULONG Size
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine attempts to read or write sectors synchronously.
|
||
|
||
Arguments:
|
||
|
||
MajorFunciton - read or write request.
|
||
DeviceExtension - FT device extension.
|
||
Buffer - pointer to the data buffer.
|
||
Offset - location of the I/O.
|
||
Size - size of the I/O in bytes.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
PIRP irp;
|
||
KEVENT event;
|
||
NTSTATUS status;
|
||
IO_STATUS_BLOCK ioStatus;
|
||
|
||
KeInitializeEvent(&event,
|
||
NotificationEvent,
|
||
FALSE);
|
||
|
||
irp = IoBuildSynchronousFsdRequest(MajorFunction,
|
||
DeviceExtension->TargetObject,
|
||
Buffer,
|
||
Size,
|
||
Offset,
|
||
&event,
|
||
&ioStatus);
|
||
|
||
if (irp == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
IoGetNextIrpStackLocation(irp)->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
|
||
|
||
status = IoCallDriver(DeviceExtension->TargetObject, irp);
|
||
if (status == STATUS_PENDING) {
|
||
|
||
KeWaitForSingleObject(&event,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER) NULL);
|
||
status = ioStatus.Status;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
FtThreadMapBadSector(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PLARGE_INTEGER ByteOffset
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called to map a single failing sector on a disk.
|
||
It performs this call synchronously and must be called under a
|
||
thread context.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the extension pointer to the failing partition.
|
||
ByteOffset - a pointer to the failing location.
|
||
|
||
Return Value:
|
||
|
||
TRUE if sector successfully mapped out.
|
||
|
||
--*/
|
||
|
||
{
|
||
PDEVICE_EXTENSION ftRootExtension =
|
||
DeviceExtension->ObjectUnion.FtRootObject->DeviceExtension;
|
||
ULONG blockAddress;
|
||
PIRP irp;
|
||
PREASSIGN_BLOCKS badBlock;
|
||
KEVENT event;
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
NTSTATUS status;
|
||
NTSTATUS ftStatus;
|
||
|
||
//
|
||
// There is an error. Attempt to map the sector.
|
||
// The following calculation truncates a large integer into a long.
|
||
//
|
||
|
||
blockAddress = (ULONG)
|
||
|
||
//
|
||
// Sum of the partition offset and the IO offset in the partition.
|
||
//
|
||
|
||
((DeviceExtension->FtUnion.Identity.PartitionOffset.QuadPart +
|
||
ByteOffset->QuadPart)/
|
||
|
||
//
|
||
// The number of bytes per sector for this device.
|
||
//
|
||
|
||
DeviceExtension->FtUnion.Identity.DiskGeometry.BytesPerSector);
|
||
|
||
//
|
||
// Allocate bad block structure.
|
||
//
|
||
|
||
badBlock = ExAllocatePool(NonPagedPool,
|
||
sizeof(REASSIGN_BLOCKS));
|
||
|
||
if (badBlock == NULL) {
|
||
DebugPrint((1, "FtThreadMapBadSector: No memory\n"));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Fill in bad block structure.
|
||
//
|
||
|
||
badBlock->Reserved = 0;
|
||
badBlock->Count = 1;
|
||
badBlock->BlockNumber[0] = blockAddress;
|
||
|
||
//
|
||
// Set event to unsignalled state.
|
||
//
|
||
|
||
KeInitializeEvent(&event,
|
||
NotificationEvent,
|
||
FALSE);
|
||
|
||
//
|
||
// Create IRP to reassign the bad block.
|
||
//
|
||
|
||
irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_REASSIGN_BLOCKS,
|
||
DeviceExtension->DeviceObject,
|
||
badBlock,
|
||
sizeof(REASSIGN_BLOCKS),
|
||
NULL,
|
||
0,
|
||
FALSE,
|
||
&event,
|
||
&ioStatusBlock);
|
||
|
||
if (!irp) {
|
||
ExFreePool(badBlock);
|
||
return FALSE;
|
||
}
|
||
|
||
if (status =
|
||
IoCallDriver(DeviceExtension->TargetObject, irp) == STATUS_PENDING) {
|
||
|
||
KeWaitForSingleObject(&event,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER)NULL);
|
||
|
||
status = ioStatusBlock.Status;
|
||
}
|
||
|
||
ExFreePool(badBlock);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
ftStatus = FT_MAP_FAILED;
|
||
|
||
} else {
|
||
|
||
ftStatus = FT_SECTOR_MAPPED;
|
||
}
|
||
|
||
//
|
||
// Log error here while status is still available.
|
||
//
|
||
|
||
FtpLogError(DeviceExtension,
|
||
ftStatus,
|
||
ioStatusBlock.Status,
|
||
blockAddress,
|
||
NULL);
|
||
|
||
return NT_SUCCESS(status);
|
||
|
||
} // FtThreadMapBadSector()
|
||
|
||
|
||
NTSTATUS
|
||
FtThreadVerifyStripe(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN PIRP Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine verifies the integrity of an SWP volume by reading the
|
||
zeroth member and comparing it to the XOR of the remaining members.
|
||
It uses the same control structure that would be passed in on a
|
||
sector verify IOCTl. The thread dispatch routine will complete the IRP.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - member zero of the stripe to verify.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
PVERIFY_INFORMATION verifyInfo = Irp->AssociatedIrp.SystemBuffer;
|
||
ULONG length;
|
||
PDEVICE_EXTENSION memberExtension;
|
||
ULONG numberOfMembers =
|
||
DeviceExtension->FtCount.NumberOfMembers;
|
||
ULONG member;
|
||
LARGE_INTEGER byteOffset;
|
||
PVOID sourceBuffer;
|
||
PVOID parityBuffer;
|
||
BOOLEAN firstRead;
|
||
ULONG bytesRemaining;
|
||
NTSTATUS status;
|
||
|
||
//
|
||
// Verify number of bytes to verify doesn't spill out of partition.
|
||
//
|
||
|
||
if (verifyInfo->StartingOffset.QuadPart + verifyInfo->Length >
|
||
DeviceExtension->FtUnion.Identity.PartitionLength.QuadPart) {
|
||
|
||
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
||
Irp->IoStatus.Information = 0;
|
||
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// Determine maximum length of verification.
|
||
//
|
||
|
||
length =
|
||
verifyInfo->Length > STRIPE_SIZE ? STRIPE_SIZE: verifyInfo->Length;
|
||
|
||
//
|
||
// Allocate buffer for member reads.
|
||
//
|
||
|
||
sourceBuffer = ExAllocatePool(NonPagedPoolCacheAligned,
|
||
ROUND_TO_PAGES(length));
|
||
|
||
if (!sourceBuffer) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyStripe: Can't allocate buffer\n"));
|
||
|
||
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
Irp->IoStatus.Information = 0;
|
||
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
//
|
||
// Allocate buffer for parity reads.
|
||
//
|
||
|
||
parityBuffer = ExAllocatePool(NonPagedPoolCacheAligned,
|
||
ROUND_TO_PAGES(length));
|
||
|
||
if (!parityBuffer) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyStripe: Can't allocate buffer\n"));
|
||
|
||
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
Irp->IoStatus.Information = 0;
|
||
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
//
|
||
// Set up byte count.
|
||
//
|
||
|
||
bytesRemaining = verifyInfo->Length;
|
||
|
||
//
|
||
// Get starting byte offset from verify control structure.
|
||
//
|
||
|
||
byteOffset = verifyInfo->StartingOffset;
|
||
|
||
while (bytesRemaining) {
|
||
|
||
//
|
||
// Set first read indicator to TRUE.
|
||
//
|
||
|
||
firstRead = TRUE;
|
||
|
||
//
|
||
// For each nonzero member ...
|
||
//
|
||
|
||
for (member = 1; member < numberOfMembers; member++) {
|
||
|
||
//
|
||
// Get the device extension for this member.
|
||
//
|
||
|
||
memberExtension = FtpGetMemberDeviceExtension(DeviceExtension,
|
||
(USHORT)member);
|
||
|
||
if (!NT_SUCCESS(status =
|
||
FtThreadReadWriteSectors(IRP_MJ_READ,
|
||
memberExtension,
|
||
sourceBuffer,
|
||
&byteOffset,
|
||
length))) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyStripe: Read member %x failed at offset %x:%x (%x)\n",
|
||
memberExtension->MemberRole,
|
||
byteOffset.HighPart,
|
||
byteOffset.LowPart,
|
||
status));
|
||
|
||
break;
|
||
|
||
} else {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyStripe: Read member %x success\n",
|
||
memberExtension->MemberRole));
|
||
}
|
||
|
||
//
|
||
// Check for first read.
|
||
//
|
||
|
||
if (firstRead) {
|
||
|
||
//
|
||
// Copy first read data into buffer.
|
||
//
|
||
|
||
RtlMoveMemory(parityBuffer, sourceBuffer, length);
|
||
firstRead = FALSE;
|
||
|
||
} else {
|
||
|
||
ULONG i;
|
||
|
||
//
|
||
// XOR target buffer with result buffer.
|
||
//
|
||
|
||
for (i=0; i<length/4; i++) {
|
||
((PULONG)parityBuffer)[i] ^= ((PULONG)sourceBuffer)[i];
|
||
}
|
||
}
|
||
|
||
} // end for (member ...)
|
||
|
||
//
|
||
// Parity buffer has XOR of nonzero members.
|
||
// Read zeroeth member now.
|
||
//
|
||
|
||
if (!NT_SUCCESS(status =
|
||
FtThreadReadWriteSectors(IRP_MJ_READ,
|
||
DeviceExtension,
|
||
sourceBuffer,
|
||
&byteOffset,
|
||
length))) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyStripe: Read member 0 failed at offset %x:%x (%x)\n",
|
||
byteOffset.HighPart,
|
||
byteOffset.LowPart,
|
||
status));
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Compare XOR of nonzero members with zero member.
|
||
//
|
||
|
||
if (RtlCompareMemory(parityBuffer,
|
||
sourceBuffer,
|
||
length) != length) {
|
||
|
||
DebugPrint((1,
|
||
"FtThreadVerifyStripe: Failed at offset %x:%x\n",
|
||
byteOffset.HighPart,
|
||
byteOffset.LowPart));
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Adjust byte offset for next stripe.
|
||
//
|
||
|
||
byteOffset.QuadPart += length;
|
||
|
||
} // end while (bytesRemaining)
|
||
|
||
//
|
||
// Complete IRP.
|
||
//
|
||
|
||
Irp->IoStatus.Status = status;
|
||
Irp->IoStatus.Information = verifyInfo->Length - bytesRemaining;
|
||
|
||
//
|
||
// Free buffers.
|
||
//
|
||
|
||
ExFreePool(sourceBuffer);
|
||
ExFreePool(parityBuffer);
|
||
|
||
return status;
|
||
|
||
} // end FtThreadVerifyStripe()
|
||
|
||
VOID
|
||
FtThreadStripeRecovery(
|
||
IN PRCB Rcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the routine that drives error recovery in SWP volumes.
|
||
|
||
Arguments:
|
||
|
||
Rcb - Request control block for failing request.
|
||
|
||
Return Value:
|
||
|
||
Nothing.
|
||
|
||
--*/
|
||
|
||
{
|
||
PDEVICE_EXTENSION zeroExtension = Rcb->ZeroExtension;
|
||
NTSTATUS status = Rcb->IoStatus.Status;
|
||
|
||
if (!FsRtlIsTotalDeviceFailure(status)) {
|
||
|
||
StripeRecoverFailedIo(zeroExtension,
|
||
Rcb);
|
||
return;
|
||
}
|
||
|
||
if (status == STATUS_DEVICE_NOT_CONNECTED) {
|
||
|
||
//
|
||
// Orphan this member as this is an error
|
||
// that is unlikely to go away. This is done
|
||
// locally only. The registry is only updated
|
||
// on failed write commands. Note that members
|
||
// that are regenerating are not orphaned.
|
||
//
|
||
|
||
if ((Rcb->MemberExtension->MemberState == Healthy) &&
|
||
!(Rcb->Flags & RCB_FLAGS_REGENERATION_ACTIVE)) {
|
||
Rcb->MemberExtension->MemberState = Orphaned;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Check for write request. The completion routine is
|
||
// a better indicator of a write request as the actual
|
||
// writes never go through recovery.
|
||
//
|
||
|
||
if (Rcb->CompletionRoutine == StripeWithParityIoCompletion) {
|
||
|
||
//
|
||
// Check if this member is orphaned. Members are orphaned only
|
||
// if a write fails. Do not orphan when regeneration is in progress.
|
||
//
|
||
|
||
if ((zeroExtension->VolumeState != FtHasOrphan) &&
|
||
!(Rcb->Flags & RCB_FLAGS_REGENERATION_ACTIVE)) {
|
||
|
||
//
|
||
// Update registry with orphan information.
|
||
//
|
||
|
||
FtpOrphanMember(Rcb->MemberExtension);
|
||
}
|
||
}
|
||
|
||
StripeDeviceFailure(zeroExtension,
|
||
Rcb);
|
||
|
||
return;
|
||
|
||
} // FtThreadStripeRecovery()
|
||
|
||
|
||
VOID
|
||
FtThreadSetVerifyState(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN BOOLEAN State
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine constructs and Irp and sends it to the target for the
|
||
device object provided to set the verify status on the "real" device.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the FT device extension that will locate the target object.
|
||
State - TRUE == turn on verify bit
|
||
FALSE == turn off verify bit
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PIRP irp;
|
||
ULONG controlCode;
|
||
KEVENT event;
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
|
||
controlCode = State ? IOCTL_DISK_INTERNAL_SET_VERIFY : IOCTL_DISK_INTERNAL_CLEAR_VERIFY;
|
||
|
||
//
|
||
// Set event to unsignalled state.
|
||
//
|
||
|
||
KeInitializeEvent(&event,
|
||
NotificationEvent,
|
||
FALSE);
|
||
|
||
//
|
||
// Create IRP for the ioctl.
|
||
//
|
||
|
||
irp = IoBuildDeviceIoControlRequest(controlCode,
|
||
DeviceExtension->DeviceObject,
|
||
NULL,
|
||
0,
|
||
NULL,
|
||
0,
|
||
FALSE,
|
||
&event,
|
||
&ioStatusBlock);
|
||
|
||
if (!irp) {
|
||
return;
|
||
}
|
||
|
||
irp->RequestorMode = KernelMode;
|
||
|
||
if (IoCallDriver(DeviceExtension->TargetObject, irp) == STATUS_PENDING) {
|
||
|
||
KeWaitForSingleObject(&event,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER)NULL);
|
||
|
||
}
|
||
|
||
return;
|
||
}
|