1604 lines
47 KiB
C
1604 lines
47 KiB
C
/*++
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
config.c
|
||
|
||
Abstract:
|
||
|
||
This module provides the configuration information to the FT
|
||
device driver.
|
||
|
||
Currently it is implemented using the ZwXxx routines to read
|
||
information from the registry.
|
||
|
||
The format of the registry information is described in the two
|
||
include files ntdskreg.h and ntddft.h. The registry information
|
||
is stored in a single value within the key \registry\machine\system\disk.
|
||
The value name is "information". The format of this single value is
|
||
a collection of "compressed" structures. Compressed structures are
|
||
multi element structures where the following structure starts at the
|
||
end of the preceeding structure. The picture below attempts to display
|
||
this:
|
||
|
||
+---------------------------------------+
|
||
| |
|
||
| DISK_CONFIG_HEADER |
|
||
| contains the offset to the |
|
||
| DISK_REGISTRY header and the |
|
||
| FT_REGISTRY header. |
|
||
+---------------------------------------+
|
||
| |
|
||
| DISK_REGISTRY |
|
||
| contains a count of disks |
|
||
+---------------------------------------+
|
||
| |
|
||
| DISK_DESCRIPTION |
|
||
| contains a count of partitions |
|
||
+---------------------------------------+
|
||
| |
|
||
| PARTITION_DESCRIPTION |
|
||
| entry for each partition |
|
||
+---------------------------------------+
|
||
| |
|
||
= More DISK_DESCRIPTION plus =
|
||
= PARTITION_DESCRIPTIONS for =
|
||
= the number of disks in the =
|
||
= system. Note, the second disk =
|
||
= description starts in the "n"th =
|
||
= partition location of the memory =
|
||
= area. This is the meaning of =
|
||
= "compressed" format. =
|
||
| |
|
||
+---------------------------------------+
|
||
| |
|
||
| FT_REGISTRY |
|
||
| contains a count of FT components |
|
||
| this is located by an offset in |
|
||
| the DISK_CONFIG_HEADER |
|
||
+---------------------------------------+
|
||
| |
|
||
| FT_DESCRIPTION |
|
||
| contains a count of FT members |
|
||
+---------------------------------------+
|
||
| |
|
||
| FT_MEMBER |
|
||
| entry for each member |
|
||
+---------------------------------------+
|
||
| |
|
||
= More FT_DESCRIPTION plus =
|
||
= FT_MEMBER entries for the number =
|
||
= of FT compenents in the system =
|
||
| |
|
||
+---------------------------------------+
|
||
|
||
This packing of structures is done for two reasons:
|
||
|
||
1. to conserve space in the registry. If there are only two partitions
|
||
on a disk then there are only two PARTITION_DESCRIPTIONs in the
|
||
registry for that disk.
|
||
2. to not impose a maximum on the number of items that can be described
|
||
in the registry. For example if the number of members in a stripe
|
||
set were to change from 32 to 64 there would be no effect on the
|
||
registry format, only on the UI that presents it to the user.
|
||
|
||
Author:
|
||
|
||
Bob Rinne (bobri) 2-Feb-1992
|
||
Mike Glass (mglass)
|
||
|
||
Environment:
|
||
|
||
kernel mode only
|
||
|
||
Notes:
|
||
|
||
The code to access the registry needs to allocate memory and
|
||
potentially delay execution until memory is available. Therefore
|
||
it should only be called from the context of a thread (initialization
|
||
or FT created).
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "ntddk.h"
|
||
#include "ftdisk.h"
|
||
|
||
#ifdef POOL_TAGGING
|
||
#ifdef ExAllocatePool
|
||
#undef ExAllocatePool
|
||
#endif
|
||
#define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,' CtF')
|
||
#endif
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, FtpConfigure)
|
||
#endif
|
||
|
||
NTSTATUS
|
||
FtpCreateMissingDevice(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN FT_TYPE Type,
|
||
IN USHORT FtGroup,
|
||
IN USHORT MemberRole,
|
||
IN OUT PDEVICE_EXTENSION *DeviceExtensionPtr
|
||
);
|
||
|
||
VOID
|
||
FtpMarkMirrorPartitionType(
|
||
IN PDEVICE_EXTENSION DeviceExtension
|
||
);
|
||
|
||
//
|
||
// Size of default work area allocated when getting information from
|
||
// the registry.
|
||
//
|
||
|
||
#define WORK_AREA 4096
|
||
|
||
|
||
NTSTATUS
|
||
FtpOpenKey(
|
||
IN PHANDLE HandlePtr,
|
||
IN PUNICODE_STRING KeyName
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Routine to open a key in the configuration registry.
|
||
|
||
Arguments:
|
||
|
||
HandlePtr - Pointer to a location for the resulting handle.
|
||
KeyName - Ascii string for the name of the key.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
OBJECT_ATTRIBUTES objectAttributes;
|
||
|
||
memset(&objectAttributes, 0, sizeof(OBJECT_ATTRIBUTES));
|
||
InitializeObjectAttributes(&objectAttributes,
|
||
KeyName,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL);
|
||
|
||
status = ZwOpenKey(HandlePtr,
|
||
KEY_READ | KEY_WRITE,
|
||
&objectAttributes);
|
||
return status;
|
||
} // FtpOpenKey
|
||
|
||
|
||
NTSTATUS
|
||
FtpReturnRegistryInformation(
|
||
IN PUCHAR ValueName,
|
||
IN OUT PVOID *FreePoolAddress,
|
||
IN OUT PVOID *Information
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine queries the configuration registry
|
||
for the configuration information of the FT subsystem.
|
||
NOTE: It must be called with a thread context since it calls into
|
||
the thread logic to insure a buffer is allocated for the data.
|
||
|
||
Arguments:
|
||
|
||
ValueName - an Ascii string for the value name to be returned.
|
||
FreePoolAddress - a pointer to a pointer for the address to free when
|
||
done using information.
|
||
Information - a pointer to a pointer for the information.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
HANDLE handle;
|
||
ULONG requestLength;
|
||
ULONG resultLength;
|
||
STRING string;
|
||
UNICODE_STRING unicodeName;
|
||
PKEY_VALUE_FULL_INFORMATION keyValueInformation;
|
||
|
||
RtlInitString(&string, DISK_REGISTRY_KEY);
|
||
|
||
status = RtlAnsiStringToUnicodeString(&unicodeName,
|
||
&string,
|
||
TRUE);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
status = FtpOpenKey(&handle,
|
||
&unicodeName);
|
||
RtlFreeUnicodeString(&unicodeName);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
RtlInitString(&string,
|
||
ValueName);
|
||
status = RtlAnsiStringToUnicodeString(&unicodeName,
|
||
&string,
|
||
TRUE);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
requestLength = WORK_AREA;
|
||
|
||
while (1) {
|
||
|
||
keyValueInformation = (PKEY_VALUE_FULL_INFORMATION)
|
||
ExAllocatePool(NonPagedPool,
|
||
requestLength);
|
||
|
||
status = ZwQueryValueKey(handle,
|
||
&unicodeName,
|
||
KeyValueFullInformation,
|
||
keyValueInformation,
|
||
requestLength,
|
||
&resultLength);
|
||
|
||
if (status == STATUS_BUFFER_OVERFLOW) {
|
||
|
||
//
|
||
// Try to get a buffer big enough.
|
||
//
|
||
|
||
ExFreePool(keyValueInformation);
|
||
requestLength += 256;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
RtlFreeUnicodeString(&unicodeName);
|
||
ZwClose(handle);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
if (keyValueInformation->DataLength != 0) {
|
||
|
||
//
|
||
// Return the pointers to the caller.
|
||
//
|
||
|
||
*Information =
|
||
(PUCHAR)keyValueInformation + keyValueInformation->DataOffset;
|
||
*FreePoolAddress = keyValueInformation;
|
||
} else {
|
||
|
||
//
|
||
// Treat as a no value case.
|
||
//
|
||
|
||
DebugPrint((3, "FtpReturnRegistryInformation: No Size\n"));
|
||
ExFreePool(keyValueInformation);
|
||
status = STATUS_OBJECT_NAME_NOT_FOUND;
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// Free the memory on failure.
|
||
//
|
||
|
||
DebugPrint((3, "FtpReturnRegistryInformation: No Value => %x\n",
|
||
status));
|
||
ExFreePool(keyValueInformation);
|
||
}
|
||
|
||
return status;
|
||
|
||
} // FtpReturnRegistryInformation
|
||
|
||
|
||
NTSTATUS
|
||
FtpWriteRegistryInformation(
|
||
IN PUCHAR ValueName,
|
||
IN PVOID Information,
|
||
IN ULONG InformationLength
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine writes the configuration registry
|
||
for the configuration information of the FT subsystem.
|
||
|
||
Arguments:
|
||
|
||
ValueName - an Ascii string for the value name to be written.
|
||
Information - a pointer to a buffer area containing the information.
|
||
InformationLength - the length of the buffer area.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
HANDLE handle;
|
||
STRING string;
|
||
UNICODE_STRING unicodeName;
|
||
|
||
RtlInitString(&string, DISK_REGISTRY_KEY);
|
||
|
||
status = RtlAnsiStringToUnicodeString(&unicodeName,
|
||
&string,
|
||
TRUE);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
status = FtpOpenKey(&handle,
|
||
&unicodeName);
|
||
RtlFreeUnicodeString(&unicodeName);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
|
||
RtlInitString(&string,
|
||
ValueName);
|
||
status = RtlAnsiStringToUnicodeString(&unicodeName,
|
||
&string,
|
||
TRUE);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
status = ZwSetValueKey(handle,
|
||
&unicodeName,
|
||
0,
|
||
REG_BINARY,
|
||
Information,
|
||
InformationLength);
|
||
RtlFreeUnicodeString(&unicodeName);
|
||
|
||
//
|
||
// Force this out to disk.
|
||
//
|
||
|
||
ZwFlushKey(handle);
|
||
ZwClose(handle);
|
||
}
|
||
|
||
return status;
|
||
|
||
} // FtpWriteRegistryInformation
|
||
|
||
|
||
#define TOO_MANY_BAD_MEMBERS 2
|
||
|
||
VOID
|
||
FtpConfigure(
|
||
IN PDEVICE_EXTENSION FtRootExtension,
|
||
IN BOOLEAN MaintenanceMode
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine queries the configuration registry
|
||
for the configuration information of the FT subsystem,
|
||
then proceeds to locate all FT members defined in the
|
||
registry and link FT device extensions to create the
|
||
FT components.
|
||
|
||
WARNING: How does the potential registry update that this routine
|
||
performs get synchronized with a registry update due to orphaning
|
||
an existing FT set? This can happen in the dynamic partitioning
|
||
case.
|
||
|
||
Arguments:
|
||
|
||
FtRootExtension - pointer to the device extension for the root of
|
||
the FT device list.
|
||
MaintenanceMode - Indicates that this is a maintenance invocation.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
ULONG index;
|
||
ULONG member;
|
||
ULONG badMembers;
|
||
PVOID freePoolAddress;
|
||
PDEVICE_EXTENSION currentMember;
|
||
PDEVICE_EXTENSION previousMember;
|
||
PDEVICE_EXTENSION zeroMember;
|
||
PDISK_CONFIG_HEADER registry;
|
||
PDISK_PARTITION diskPartition;
|
||
PDISK_PARTITION orphanPartition;
|
||
PFT_REGISTRY ftRegistry;
|
||
PFT_DESCRIPTION ftDescription;
|
||
PFT_MEMBER_DESCRIPTION ftMember;
|
||
PFT_REGENERATE_REGION regenerateRegion;
|
||
ULONG alignmentRequirement;
|
||
BOOLEAN writeRegistryBack = FALSE;
|
||
BOOLEAN haveMemberToRegenerate = FALSE;
|
||
BOOLEAN dirtyShutdown = FALSE;
|
||
|
||
//
|
||
// Find the FT section in the configuration.
|
||
//
|
||
|
||
status = FtpReturnRegistryInformation(DISK_REGISTRY_VALUE,
|
||
&freePoolAddress,
|
||
(PVOID) ®istry);
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// No registry data.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
if (registry->FtInformationSize == 0) {
|
||
|
||
//
|
||
// No FT components in the registry.
|
||
//
|
||
|
||
ExFreePool(freePoolAddress);
|
||
return;
|
||
}
|
||
|
||
if (!MaintenanceMode) {
|
||
|
||
//
|
||
// Determine if system was shutdown properly.
|
||
//
|
||
|
||
if (registry->DirtyShutdown) {
|
||
|
||
//
|
||
// Log that a dirty shutdown was detected.
|
||
//
|
||
|
||
dirtyShutdown = TRUE;
|
||
FtpLogError(FtRootExtension,
|
||
FT_DIRTY_SHUTDOWN,
|
||
0,
|
||
0,
|
||
NULL);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Write back registry now setting dirty flag.
|
||
//
|
||
|
||
registry->DirtyShutdown = TRUE;
|
||
|
||
FtpWriteRegistryInformation(DISK_REGISTRY_VALUE,
|
||
registry,
|
||
registry->FtInformationOffset +
|
||
registry->FtInformationSize);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Construct the necessary links for the NTFT volumes in the system.
|
||
//
|
||
|
||
ftRegistry = (PFT_REGISTRY)
|
||
((PUCHAR)registry + registry->FtInformationOffset);
|
||
ftDescription = &ftRegistry->FtDescription[0];
|
||
|
||
for (index = 0; index < (ULONG) ftRegistry->NumberOfComponents; index++) {
|
||
|
||
previousMember = NULL;
|
||
badMembers = 0;
|
||
orphanPartition = NULL;
|
||
zeroMember = NULL;
|
||
for (member = 0;
|
||
member < (ULONG) ftDescription->NumberOfMembers;
|
||
member++) {
|
||
|
||
ftMember = &ftDescription->FtMemberDescription[member];
|
||
diskPartition = FtpFindPartitionRegistry(registry,
|
||
ftMember);
|
||
//
|
||
// Find a corresponding device extension for this registry
|
||
// entry.
|
||
//
|
||
|
||
currentMember = FtpFindDeviceExtension(FtRootExtension,
|
||
ftMember->Signature,
|
||
diskPartition->StartingOffset,
|
||
diskPartition->Length);
|
||
if (currentMember == NULL) {
|
||
|
||
//
|
||
// Failure to find member of FT volume.
|
||
// Create a fake member device extension and mark it as
|
||
// orphaned.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtpConfigure: Missing NTFT member %x\n",
|
||
member));
|
||
|
||
//
|
||
// Create a place holder for this device extension in the
|
||
// FT chain.
|
||
//
|
||
|
||
status = FtpCreateMissingDevice(FtRootExtension,
|
||
ftDescription->Type,
|
||
diskPartition->FtGroup,
|
||
diskPartition->FtMember,
|
||
¤tMember);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// Set zero member to NULL so this set can't be accessed.
|
||
//
|
||
|
||
zeroMember = NULL;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Indicate internally that the member is missing.
|
||
//
|
||
|
||
currentMember->MemberState = Orphaned;
|
||
|
||
//
|
||
// Notify the user via the event log.
|
||
//
|
||
|
||
FtpLogError(currentMember,
|
||
FT_MISSING_MEMBER,
|
||
0,
|
||
0,
|
||
NULL);
|
||
|
||
} else {
|
||
|
||
if (currentMember->Type == NotAnFtMember) {
|
||
|
||
//
|
||
// Only let new sets be created inside this routine
|
||
//
|
||
|
||
currentMember->MemberState = diskPartition->FtState;
|
||
currentMember->Flags &= ~FTF_CONFIGURATION_CHANGED;
|
||
} else {
|
||
|
||
if (currentMember->Flags & FTF_CONFIGURATION_CHANGED) {
|
||
|
||
//
|
||
// This set is expected to change so it will be
|
||
// "reconstructed" at this time. The assumption
|
||
// is that the set is locked so no I/O may be
|
||
// occurring on the set will it is being built.
|
||
//
|
||
|
||
currentMember->MemberState = diskPartition->FtState;
|
||
currentMember->Flags &= ~FTF_CONFIGURATION_CHANGED;
|
||
|
||
} else {
|
||
|
||
//
|
||
// If this set is not marked for change then there
|
||
// is the possibility of I/O being active on the
|
||
// set so do not modify the set.
|
||
//
|
||
|
||
member = ftDescription->NumberOfMembers;
|
||
goto NextGroup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If this partition is going to be something other than
|
||
// the zeroth member of the set, clear the verify flag.
|
||
//
|
||
|
||
if (diskPartition->FtMember) {
|
||
|
||
//
|
||
// For dynamic partitioning, it is possible for the target
|
||
// device object to be in the "do verify" state. Since
|
||
// this object is now going to be used in a new FT set,
|
||
// and is not going to be the "real device" object,
|
||
// remove this flag from the target object.
|
||
//
|
||
|
||
FtThreadSetVerifyState(currentMember, FALSE);
|
||
}
|
||
|
||
//
|
||
// Since this extension is being used for a new FT set
|
||
// insure that the next member pointer is NULL.
|
||
//
|
||
|
||
currentMember->NextMember = NULL;
|
||
}
|
||
|
||
//
|
||
// If these fields are already set up and haven't changed
|
||
// then leave them alone.
|
||
//
|
||
|
||
if (!MaintenanceMode || diskPartition->Modified) {
|
||
|
||
//
|
||
// Fill in as much of the member device extension as possible.
|
||
//
|
||
|
||
currentMember->Type = ftDescription->Type;
|
||
currentMember->FtGroup = diskPartition->FtGroup;
|
||
currentMember->MemberRole = diskPartition->FtMember;
|
||
currentMember->FtCount.NumberOfMembers = ftDescription->NumberOfMembers;
|
||
currentMember->FtUnion.Identity.Signature = ftMember->Signature;
|
||
currentMember->FtUnion.Identity.PartitionOffset =
|
||
diskPartition->StartingOffset;
|
||
currentMember->FtUnion.Identity.PartitionLength =
|
||
diskPartition->Length;
|
||
currentMember->FtUnion.Identity.OriginalLength =
|
||
diskPartition->FtLength;
|
||
currentMember->ObjectUnion.FtRootObject =
|
||
FtRootExtension->DeviceObject;
|
||
|
||
currentMember->WritePolicy = Parallel;
|
||
currentMember->ReadPolicy = ReadPrimary;
|
||
currentMember->IgnoreReadPolicy = FALSE;
|
||
currentMember->Flags = FtRootExtension->Flags;
|
||
}
|
||
|
||
//
|
||
// Perform member specific work. If this is member zero,
|
||
// set up the variables for it. Otherwise check for member
|
||
// one of an Stripe with parity or Mirror and member zero
|
||
// is orphaned.
|
||
//
|
||
|
||
if (currentMember->MemberRole == 0) {
|
||
|
||
//
|
||
// Establish zero member and set state to initializing.
|
||
// This is a very primitive mechanism to synchronize this
|
||
// routine with active IO.
|
||
//
|
||
// What about dynamic configuration? (ie Maintanance mode)
|
||
//
|
||
|
||
zeroMember = currentMember;
|
||
zeroMember->VolumeState = FtInitializing;
|
||
|
||
//
|
||
// Set up the regeneration region if necessary.
|
||
//
|
||
|
||
regenerateRegion = &zeroMember->RegenerateRegion;
|
||
|
||
if (!(zeroMember->Flags & FTF_REGENERATION_REGION_INITIALIZED)) {
|
||
KeInitializeSpinLock(¤tMember->IrpCountSpinLock);
|
||
InitializeRegenerateRegion(zeroMember, regenerateRegion);
|
||
zeroMember->Flags |= FTF_REGENERATION_REGION_INITIALIZED;
|
||
}
|
||
|
||
//
|
||
// Check if Stripe or StripeWithParity to determine if
|
||
// lookaside listhead should be initialized.
|
||
//
|
||
|
||
switch (zeroMember->Type) {
|
||
|
||
case Stripe:
|
||
|
||
//
|
||
// Initialize RCB lookaside listhead if necessary.
|
||
//
|
||
|
||
if (!(FtRootExtension->Flags & FTF_RCB_LOOKASIDE_ALLOCATED)) {
|
||
|
||
//
|
||
// Set up lookaside listhead for RCBs.
|
||
//
|
||
|
||
FtpInitializeRcbLookasideListHead(FtRootExtension);
|
||
|
||
//
|
||
// Zero active stripe recovery thread count.
|
||
//
|
||
|
||
FtRootExtension->StripeThreadCount = 0;
|
||
|
||
//
|
||
// Indicate lookaside listhead is ready.
|
||
//
|
||
|
||
FtRootExtension->Flags |= FTF_RCB_LOOKASIDE_ALLOCATED;
|
||
}
|
||
|
||
break;
|
||
|
||
case StripeWithParity:
|
||
|
||
//
|
||
// Initialize RCB lookaside listhead if necessary.
|
||
//
|
||
|
||
if (!(FtRootExtension->Flags & FTF_RCB_LOOKASIDE_ALLOCATED)) {
|
||
|
||
//
|
||
// Set up lookaside listhead for RCBs.
|
||
//
|
||
|
||
FtpInitializeRcbLookasideListHead(FtRootExtension);
|
||
|
||
//
|
||
// Zero active stripe recovery thread count.
|
||
//
|
||
|
||
FtRootExtension->StripeThreadCount = 0;
|
||
|
||
//
|
||
// Indicate lookaside listhead is ready.
|
||
//
|
||
|
||
FtRootExtension->Flags |= FTF_RCB_LOOKASIDE_ALLOCATED;
|
||
}
|
||
|
||
//
|
||
// Initialize restart thread if necessary.
|
||
//
|
||
|
||
if (!(FtRootExtension->Flags & FTF_RESTART_THREAD_STARTED)) {
|
||
|
||
FtCreateThread(FtRootExtension,
|
||
&FtRootExtension->RestartThread,
|
||
(PKSTART_ROUTINE)FtRestartThread);
|
||
|
||
FtRootExtension->Flags |= FTF_RESTART_THREAD_STARTED;
|
||
}
|
||
|
||
//
|
||
// Allocate emergency buffers to handle the situation
|
||
// where there is no memory for some operation so the
|
||
// cache manager flushes pages to create pool, but the
|
||
// FTDISK driver doesn't have the extra buffers to
|
||
// process the request.
|
||
//
|
||
|
||
if (!(FtRootExtension->Flags & FTF_EMERGENCY_BUFFER_ALLOCATED)) {
|
||
FtRootExtension->ParityBuffers =
|
||
ExAllocatePool(NonPagedPoolCacheAligned,
|
||
STRIPE_SIZE * 2);
|
||
|
||
//
|
||
// Set bit showing emergency buffer is allocated.
|
||
//
|
||
|
||
if (FtRootExtension->ParityBuffers) {
|
||
FtRootExtension->Flags |= FTF_EMERGENCY_BUFFER_ALLOCATED;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Fall through to common code.
|
||
//
|
||
|
||
case Mirror:
|
||
|
||
//
|
||
// Start recovery thread if necessary.
|
||
//
|
||
|
||
if (!(FtRootExtension->Flags & FTF_RECOVERY_THREAD_STARTED)) {
|
||
|
||
FtCreateThread(FtRootExtension,
|
||
&FtRootExtension->FtUnion.Thread,
|
||
(PKSTART_ROUTINE)FtRecoveryThread);
|
||
|
||
FtRootExtension->Flags |= FTF_RECOVERY_THREAD_STARTED;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
} else {
|
||
|
||
if (currentMember->Type == Mirror && !MaintenanceMode) {
|
||
|
||
//
|
||
// If the secondary mirror is smaller than the primary,
|
||
// this is a configuration error.
|
||
//
|
||
|
||
if (currentMember->FtUnion.Identity.PartitionLength.QuadPart <
|
||
previousMember->FtUnion.Identity.PartitionLength.QuadPart) {
|
||
|
||
//
|
||
// Orphan this member and set it up so it will
|
||
// be written back to the registry.
|
||
//
|
||
|
||
currentMember->MemberState = Orphaned;
|
||
orphanPartition = diskPartition;
|
||
diskPartition->FtState = Orphaned;
|
||
}
|
||
|
||
if (((currentMember->FtUnion.Identity.PartitionType & VALID_NTFT) == VALID_NTFT) &&
|
||
(zeroMember->MemberState != Orphaned)) {
|
||
|
||
//
|
||
// The system was booted from a revived primary partition
|
||
// so the hives say the mirror is ok, when in fact they
|
||
// are not. The "real" image of the hives are on the
|
||
// shadow. Force the user to boot from the shadow.
|
||
//
|
||
|
||
KeBugCheckEx(FTDISK_INTERNAL_ERROR,
|
||
(ULONG)zeroMember,
|
||
(ULONG)zeroMember->Type,
|
||
(ULONG)zeroMember->FtGroup,
|
||
zeroMember->FtUnion.Identity.Signature);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set zero member address in device extension.
|
||
//
|
||
|
||
currentMember->ZeroMember = zeroMember;
|
||
|
||
//
|
||
// If member orphaned or regenerating, increment bad member counter.
|
||
//
|
||
|
||
if (currentMember->MemberState == Orphaned) {
|
||
|
||
zeroMember->VolumeState = FtHasOrphan;
|
||
orphanPartition = diskPartition;
|
||
badMembers++;
|
||
|
||
} else if (currentMember->MemberState == Regenerating) {
|
||
|
||
zeroMember->VolumeState = FtRegenerating;
|
||
badMembers++;
|
||
}
|
||
|
||
//
|
||
// Point all members to the zero member regeneration region.
|
||
//
|
||
|
||
currentMember->RegenerateRegionForGroup = regenerateRegion;
|
||
|
||
//
|
||
// Link the current member.
|
||
//
|
||
|
||
if (previousMember) {
|
||
previousMember->NextMember = currentMember;
|
||
}
|
||
previousMember = currentMember;
|
||
|
||
} // end for (member...)
|
||
|
||
//
|
||
// Perform sanity check.
|
||
//
|
||
|
||
if (zeroMember == NULL) {
|
||
|
||
//
|
||
// Log this error. Bad configuration information.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtpConfigure: Bad configuration\n"));
|
||
|
||
FtpLogError(FtRootExtension,
|
||
FT_BAD_CONFIGURATION,
|
||
0,
|
||
0,
|
||
NULL);
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Take appropriate action based on number of bad members.
|
||
//
|
||
|
||
switch (badMembers) {
|
||
|
||
case 0:
|
||
|
||
if (zeroMember->MemberState == Initializing) {
|
||
|
||
//
|
||
// Create and start a system thread to initialize this set.
|
||
//
|
||
|
||
zeroMember->VolumeState = FtInitializing;
|
||
FtThreadStartNewThread(zeroMember,
|
||
FT_INITIALIZE_SET,
|
||
NULL);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Set volume state to healthy.
|
||
//
|
||
|
||
zeroMember->VolumeState = FtStateOk;
|
||
|
||
//
|
||
// Check for dirty shutdown. Dirty shutdowns can
|
||
// cause primary and secondary data to be out of
|
||
// sync. The dirty flag has already been set in
|
||
// the registry for this boot. A clean shutdown
|
||
// clears the dirty flag.
|
||
//
|
||
|
||
if (dirtyShutdown) {
|
||
|
||
//
|
||
// Check if fault-tolerant FT type.
|
||
//
|
||
|
||
if ((zeroMember->Type == StripeWithParity) ||
|
||
(zeroMember->Type == Mirror)) {
|
||
|
||
//
|
||
// Synchronize primary and secondary data.
|
||
//
|
||
|
||
FtThreadStartNewThread(zeroMember,
|
||
FT_SYNC_REDUNDANT_COPY,
|
||
NULL);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 1:
|
||
|
||
//
|
||
// If this is a stripe or volume set then is must be disabled. If
|
||
// a volume that needs initialization has a bad member it should
|
||
// be disabled as well.
|
||
//
|
||
|
||
if (zeroMember->Type == Stripe ||
|
||
zeroMember->Type == VolumeSet ||
|
||
zeroMember->MemberState == Initializing) {
|
||
|
||
//
|
||
// Change volume state to disabled and log error.
|
||
//
|
||
|
||
zeroMember->VolumeState = FtDisabled;
|
||
FtpLogError(zeroMember,
|
||
FT_CANT_USE_SET,
|
||
0,
|
||
0,
|
||
NULL);
|
||
|
||
} else if (zeroMember->VolumeState == FtRegenerating) {
|
||
|
||
//
|
||
// Create and start the system thread to do the regenerate.
|
||
//
|
||
|
||
FtThreadStartNewThread(zeroMember,
|
||
FT_REGENERATE,
|
||
NULL);
|
||
|
||
} else if (zeroMember->VolumeState == FtHasOrphan) {
|
||
|
||
//
|
||
// If the zero member is orphaned then the drive letter
|
||
// assignment may have to be switched to the next member.
|
||
//
|
||
|
||
if (zeroMember->MemberState == Orphaned) {
|
||
|
||
//
|
||
// Need to assign the drive letter to the first
|
||
// member of the set after the zeroth member.
|
||
//
|
||
|
||
ftMember = &ftDescription->FtMemberDescription[1];
|
||
diskPartition = FtpFindPartitionRegistry(registry,
|
||
ftMember);
|
||
diskPartition->AssignDriveLetter = TRUE;
|
||
orphanPartition->AssignDriveLetter = FALSE;
|
||
|
||
if (zeroMember->Type == Mirror) {
|
||
|
||
//
|
||
// Make sure the partition type is marked so if
|
||
// the user attempts to boot from the primary
|
||
// it will fail.
|
||
//
|
||
|
||
if (zeroMember->NextMember) {
|
||
FtpMarkMirrorPartitionType(zeroMember->NextMember);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Write back registry with new orphan informaiton.
|
||
//
|
||
|
||
orphanPartition->FtState = Orphaned;
|
||
ftDescription->FtVolumeState = FtHasOrphan;
|
||
writeRegistryBack = TRUE;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
//
|
||
// Too many bad members to do anything useful.
|
||
// Change volume state to disabled and log error.
|
||
//
|
||
|
||
zeroMember->VolumeState = FtDisabled;
|
||
FtpLogError(zeroMember,
|
||
FT_CANT_USE_SET,
|
||
0,
|
||
0,
|
||
NULL);
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Propogate target alignment requirements.
|
||
//
|
||
|
||
currentMember = zeroMember;
|
||
alignmentRequirement = 0;
|
||
|
||
//
|
||
// Prepare alignment mask.
|
||
//
|
||
|
||
while (currentMember) {
|
||
alignmentRequirement |=
|
||
currentMember->DeviceObject->AlignmentRequirement;
|
||
currentMember = currentMember->NextMember;
|
||
}
|
||
|
||
//
|
||
// Jam zero member with alignment requirement. If zero member
|
||
// is orphaned then set alignment requirements in device object
|
||
// which is exposed.
|
||
//
|
||
|
||
if (zeroMember->MemberState == Orphaned &&
|
||
zeroMember->NextMember) {
|
||
zeroMember->NextMember->DeviceObject->AlignmentRequirement =
|
||
alignmentRequirement;
|
||
} else {
|
||
zeroMember->DeviceObject->AlignmentRequirement =
|
||
alignmentRequirement;
|
||
}
|
||
|
||
//
|
||
// Get next group.
|
||
//
|
||
|
||
NextGroup:
|
||
ftDescription = (PFT_DESCRIPTION)
|
||
&ftDescription->FtMemberDescription[member];
|
||
|
||
} // end for each FT component
|
||
|
||
if (writeRegistryBack == TRUE) {
|
||
|
||
//
|
||
// The registry is written back if during initialization of the
|
||
// FT components it turns out that a member of a mirror set
|
||
// or stripe with parity is missing. The missing member is orphaned
|
||
// immediately in the registry.
|
||
//
|
||
|
||
FtpWriteRegistryInformation(DISK_REGISTRY_VALUE,
|
||
registry,
|
||
registry->FtInformationOffset +
|
||
registry->FtInformationSize);
|
||
}
|
||
|
||
ExFreePool(freePoolAddress);
|
||
|
||
return;
|
||
|
||
} // FtpConfigure
|
||
|
||
|
||
NTSTATUS
|
||
FtpCreateMissingDevice(
|
||
IN PDEVICE_EXTENSION FtRootExtension,
|
||
IN FT_TYPE Type,
|
||
IN USHORT FtGroup,
|
||
IN USHORT MemberRole,
|
||
IN OUT PDEVICE_EXTENSION *DeviceExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Used during FT initialization, this routine will create a device
|
||
to represent a missing member.
|
||
|
||
Arguments:
|
||
|
||
DriverObject - the driver object passed in by the system.
|
||
Type - the FT volume type.
|
||
FtGroup - the group number for the volume.
|
||
MemberRole - the member role within this group.
|
||
DeviceExtensionPtr - Place to return the pointer to the newly created
|
||
device extension.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
UCHAR nameBuffer[64];
|
||
ANSI_STRING deviceNameString;
|
||
UNICODE_STRING unicodeDeviceName;
|
||
OBJECT_ATTRIBUTES objectAttributes;
|
||
PDEVICE_EXTENSION deviceExtension;
|
||
PDEVICE_OBJECT newObject;
|
||
NTSTATUS status;
|
||
PUCHAR typeName;
|
||
PFILE_OBJECT fileObject;
|
||
|
||
//
|
||
// Get the string for the type of the missing member.
|
||
//
|
||
|
||
switch (Type) {
|
||
|
||
case Mirror:
|
||
typeName = "Mirror";
|
||
break;
|
||
|
||
case StripeWithParity:
|
||
typeName = "ParityStripe";
|
||
break;
|
||
|
||
case VolumeSet:
|
||
typeName = "VolumeSet";
|
||
break;
|
||
|
||
case Stripe:
|
||
typeName = "Stripe";
|
||
break;
|
||
|
||
default:
|
||
DebugPrint((4, "FtpCreateMissingDevice: Improper type %x\n", Type));
|
||
ASSERT(Type == Mirror);
|
||
return STATUS_UNSUCCESSFUL;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Construct the missing member name.
|
||
//
|
||
|
||
sprintf(nameBuffer,
|
||
"\\Device\\Missing%s%dMember%d",
|
||
typeName,
|
||
FtGroup,
|
||
MemberRole);
|
||
//
|
||
// Create device object for this partition.
|
||
//
|
||
|
||
RtlInitString(&deviceNameString,
|
||
nameBuffer);
|
||
status = RtlAnsiStringToUnicodeString(&unicodeDeviceName,
|
||
&deviceNameString,
|
||
TRUE);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
InitializeObjectAttributes(&objectAttributes,
|
||
&unicodeDeviceName,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL);
|
||
|
||
|
||
//
|
||
// Check if this object exists.
|
||
//
|
||
|
||
status = IoGetDeviceObjectPointer(&unicodeDeviceName,
|
||
FILE_READ_ATTRIBUTES,
|
||
&fileObject,
|
||
&newObject);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// If it doesn't exist, then create it.
|
||
//
|
||
|
||
DebugPrint((4, "FtpCreateMissingDevice: Create device %s\n", nameBuffer));
|
||
status = IoCreateDevice(FtRootExtension->ObjectUnion.FtDriverObject,
|
||
sizeof(DEVICE_EXTENSION),
|
||
&unicodeDeviceName,
|
||
FILE_DEVICE_DISK,
|
||
0,
|
||
FALSE,
|
||
&newObject);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// Give up.
|
||
//
|
||
|
||
RtlFreeUnicodeString(&unicodeDeviceName);
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Initialize the new device extension and link it on the list
|
||
// of missing devices anchored at the root extension.
|
||
//
|
||
|
||
*DeviceExtension = (PDEVICE_EXTENSION) newObject->DeviceExtension;
|
||
(*DeviceExtension)->MissingMemberChain = NULL;
|
||
(*DeviceExtension)->DeviceObject = newObject;
|
||
|
||
//
|
||
// Link this device extension into the missing member chain.
|
||
//
|
||
|
||
if ((deviceExtension = FtRootExtension->MissingMemberChain) == NULL) {
|
||
FtRootExtension->MissingMemberChain = *DeviceExtension;
|
||
} else {
|
||
|
||
while (deviceExtension->MissingMemberChain != NULL) {
|
||
deviceExtension = deviceExtension->MissingMemberChain;
|
||
}
|
||
|
||
deviceExtension->MissingMemberChain = *DeviceExtension;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Assume device extension is already linked in the missing devices
|
||
// list anchored at the root extension.
|
||
//
|
||
|
||
DebugPrint((4, "FtpCreateMissingDevice: Device %s already exists\n", nameBuffer));
|
||
*DeviceExtension = (PDEVICE_EXTENSION) newObject->DeviceExtension;
|
||
ObDereferenceObject(fileObject);
|
||
}
|
||
|
||
RtlFreeUnicodeString(&unicodeDeviceName);
|
||
return status;
|
||
}
|
||
|
||
|
||
VOID
|
||
FtpChangeMemberStateInRegistry(
|
||
IN PDEVICE_EXTENSION DeviceExtension,
|
||
IN FT_PARTITION_STATE NewState
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called either during initialization or via the
|
||
system worker thread. This routine will read the FT
|
||
registry information and mark the partition state as indicated, then
|
||
write the result.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension - the internal description of the partition to change.
|
||
NewState - the new state of the partition.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG signature = DeviceExtension->FtUnion.Identity.Signature;
|
||
FT_TYPE type = DeviceExtension->Type;
|
||
USHORT group = DeviceExtension->FtGroup;
|
||
USHORT member = DeviceExtension->MemberRole;
|
||
PDISK_CONFIG_HEADER registry;
|
||
PDISK_REGISTRY diskRegistry;
|
||
PDISK_DESCRIPTION diskDescription;
|
||
PVOID freePoolAddress;
|
||
USHORT i;
|
||
NTSTATUS status;
|
||
|
||
DebugPrint((1,
|
||
"FtpChangeMemberStateInRegistry: Called for %x %d %d %d\n",
|
||
signature,
|
||
type,
|
||
group,
|
||
member));
|
||
status = FtpReturnRegistryInformation(DISK_REGISTRY_VALUE,
|
||
&freePoolAddress,
|
||
(PVOID) ®istry);
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// No registry data.
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"FtpChangeMemberStateInRegistry: No Value => 0x%x\n",
|
||
status));
|
||
ASSERT(0);
|
||
return;
|
||
}
|
||
|
||
if (registry->FtInformationSize == 0) {
|
||
|
||
//
|
||
// No FT components in the registry.
|
||
//
|
||
|
||
ExFreePool(freePoolAddress);
|
||
DebugPrint((1, "FtpChangeMemberStateInRegistry: No FT components.\n"));
|
||
ASSERT(0);
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Find the registry information for the disk partition.
|
||
//
|
||
|
||
diskRegistry = (PDISK_REGISTRY)
|
||
((PUCHAR)registry + registry->DiskInformationOffset);
|
||
diskDescription = &diskRegistry->Disks[0];
|
||
|
||
for (i = 0; i < diskRegistry->NumberOfDisks; i++) {
|
||
|
||
DebugPrint((2,
|
||
"FtpChangeMemberStateInRegistry: Checking disk %x\n",
|
||
diskDescription->Signature));
|
||
|
||
if (diskDescription->Signature == signature) {
|
||
USHORT j;
|
||
PDISK_PARTITION diskPartition;
|
||
|
||
for (j = 0; j < diskDescription->NumberOfPartitions; j++) {
|
||
|
||
diskPartition = &diskDescription->Partitions[j];
|
||
|
||
if ((diskPartition->FtType == type) &&
|
||
(diskPartition->FtGroup == group) &&
|
||
(diskPartition->FtMember == member)) {
|
||
|
||
//
|
||
// Found the member.
|
||
//
|
||
|
||
diskPartition->FtState = NewState;
|
||
|
||
DebugPrint((2,
|
||
"FtpChangeMemberStateInRegistry: Writing new info %x\n",
|
||
signature));
|
||
FtpWriteRegistryInformation(DISK_REGISTRY_VALUE,
|
||
registry,
|
||
registry->FtInformationOffset +
|
||
registry->FtInformationSize);
|
||
ExFreePool(freePoolAddress);
|
||
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
diskDescription = (PDISK_DESCRIPTION)
|
||
&diskDescription->Partitions[diskDescription->NumberOfPartitions];
|
||
}
|
||
|
||
DebugPrint((1,
|
||
"FtpChangeMemberStateInRegistry: Did not update registry\n"));
|
||
ExFreePool(freePoolAddress);
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
FtpInitializeRcbLookasideListHead(
|
||
IN PDEVICE_EXTENSION FtRootExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine sets up a lookaside listhead for request control blocks.
|
||
|
||
Arguments:
|
||
|
||
FtRootExtension - FtRoot device extension.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
//
|
||
// Initialize Rcb lookaide listhead.
|
||
//
|
||
|
||
ExInitializeNPagedLookasideList(&FtRootExtension->RcbLookasideListHead,
|
||
NULL,
|
||
NULL,
|
||
0,
|
||
sizeof(RCB),
|
||
'crTF',
|
||
4096 / sizeof(RCB));
|
||
|
||
return;
|
||
} // end FtpInitializeRcbLookasideListHead()
|
||
|
||
|
||
NTSTATUS
|
||
FtpAttachDevices(
|
||
IN PDEVICE_OBJECT DeviceObject,
|
||
IN PIRP Irp,
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the completion routine for handling IOCTL_SET_DRIVE_LAYOUT.
|
||
|
||
Arguments:
|
||
|
||
DeviceObject - not used.
|
||
Irp - completing IRP.
|
||
Context - RCB
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
PRCB rcb = Context;
|
||
IN PDRIVE_LAYOUT_INFORMATION partitionList;
|
||
PDEVICE_EXTENSION deviceExtension = rcb->ZeroExtension;
|
||
PDEVICE_EXTENSION ftRootExtension =
|
||
deviceExtension->ObjectUnion.FtRootObject->DeviceExtension;
|
||
PDRIVER_OBJECT driverObject = ftRootExtension->ObjectUnion.FtDriverObject;
|
||
ULONG recognizedPartition = 0;
|
||
ULONG partitionNumber;
|
||
PPARTITION_INFORMATION partitionEntry;
|
||
UCHAR ntNameBuffer[64];
|
||
UCHAR ftNameBuffer[64];
|
||
NTSTATUS status;
|
||
|
||
//
|
||
// Get partition list from IRP.
|
||
//
|
||
|
||
partitionList = Irp->AssociatedIrp.SystemBuffer;
|
||
|
||
for (partitionNumber = 0; partitionNumber <
|
||
partitionList->PartitionCount; partitionNumber++) {
|
||
|
||
//
|
||
// Get pointer to partition entry.
|
||
//
|
||
|
||
partitionEntry =
|
||
&partitionList->PartitionEntry[partitionNumber];
|
||
|
||
//
|
||
// Check if partition entry describes a partition.
|
||
//
|
||
|
||
if ((partitionEntry->PartitionType != PARTITION_ENTRY_UNUSED) &&
|
||
!IsContainerPartition(partitionEntry->PartitionType)) {
|
||
|
||
//
|
||
// Bump count of recognized partitions.
|
||
//
|
||
|
||
recognizedPartition++;
|
||
|
||
//
|
||
// Check if this partition entry has changed.
|
||
//
|
||
|
||
if (partitionEntry->RewritePartition) {
|
||
|
||
//
|
||
// Create NT device name for this partition.
|
||
//
|
||
|
||
sprintf(ntNameBuffer,
|
||
"\\Device\\Harddisk%d\\Partition%d",
|
||
deviceExtension->FtUnion.Identity.DiskNumber,
|
||
recognizedPartition);
|
||
|
||
sprintf(ftNameBuffer,
|
||
"\\Device\\Harddisk%d\\Ft%d",
|
||
deviceExtension->FtUnion.Identity.DiskNumber,
|
||
recognizedPartition);
|
||
|
||
status = FtpAttach(driverObject,
|
||
ftNameBuffer,
|
||
ntNameBuffer,
|
||
&deviceExtension);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
DebugPrint((1,
|
||
"FtpAttachDevices: Couldn't attach to %s status %x\n",
|
||
ntNameBuffer,
|
||
status));
|
||
}
|
||
|
||
} // end if (partitionEntry->RewritePartition)
|
||
|
||
} // end if (partitionEntry->PartitionType ...)
|
||
|
||
} // end for (partitionNumber ...)
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // end FtpAttachDevices()
|
||
|