windows-nt/Source/XPSP1/NT/base/ntos/fstub/ex.c
2020-09-26 16:20:57 +08:00

4386 lines
101 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name:
ex.c
Abstract:
Extended routines for reading and writing new partition table types like
EFI partitioned disks.
The following routines are exported from this file:
IoCreateDisk - Initialize an empty disk.
IoWritePartitionTableEx - Write a partition table for either a
legacy AT-style disk or an EFI partitioned disk.
IoReadPartitionTableEx - Read the partition table for a disk.
IoSetPartitionInformation - Set information for a specific
partition.
Author:
Matthew D Hendel (math) 07-Sept-1999
Revision History:
--*/
#include <ntos.h>
#include <zwapi.h>
#include <hal.h>
#include <ntdddisk.h>
#include <ntddft.h>
#include <setupblk.h>
#include <stdio.h>
#include "fstub.h"
#include "efi.h"
#include "ex.h"
#include "haldisp.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, IoCreateDisk)
#pragma alloc_text(PAGE, IoReadPartitionTableEx)
#pragma alloc_text(PAGE, IoWritePartitionTableEx)
#pragma alloc_text(PAGE, IoSetPartitionInformationEx)
#pragma alloc_text(PAGE, IoUpdateDiskGeometry)
#pragma alloc_text(PAGE, IoVerifyPartitionTable)
#pragma alloc_text(PAGE, FstubSetPartitionInformationEFI)
#pragma alloc_text(PAGE, FstubReadPartitionTableMBR)
#pragma alloc_text(PAGE, FstubDetectPartitionStyle)
#pragma alloc_text(PAGE, FstubGetDiskGeometry)
#pragma alloc_text(PAGE, FstubAllocateDiskInformation)
#pragma alloc_text(PAGE, FstubFreeDiskInformation)
#pragma alloc_text(PAGE, FstubWriteBootSectorEFI)
#pragma alloc_text(PAGE, FstubConvertExtendedToLayout)
#pragma alloc_text(PAGE, FstubWritePartitionTableMBR)
#pragma alloc_text(PAGE, FstubWriteEntryEFI)
#pragma alloc_text(PAGE, FstubWriteHeaderEFI)
#pragma alloc_text(PAGE, FstubAdjustPartitionCount)
#pragma alloc_text(PAGE, FstubCreateDiskEFI)
#pragma alloc_text(PAGE, FstubCreateDiskMBR)
#pragma alloc_text(PAGE, FstubCreateDiskRaw)
#pragma alloc_text(PAGE, FstubCopyEntryEFI)
#pragma alloc_text(PAGE, FstubWritePartitionTableEFI)
#pragma alloc_text(PAGE, FstubReadHeaderEFI)
#pragma alloc_text(PAGE, FstubReadPartitionTableEFI)
#pragma alloc_text(PAGE, FstubVerifyPartitionTableEFI)
#pragma alloc_text(PAGE, FstubUpdateDiskGeometryEFI)
#pragma alloc_text(PAGE, FstubWriteSector)
#pragma alloc_text(PAGE, FstubReadSector)
#if DBG
#pragma alloc_text(PAGE, FstubDbgPrintPartition)
#pragma alloc_text(PAGE, FstubDbgPrintDriveLayout)
#pragma alloc_text(PAGE, FstubDbgPrintPartitionEx)
#pragma alloc_text(PAGE, FstubDbgPrintDriveLayoutEx)
#pragma alloc_text(PAGE, FstubDbgPrintSetPartitionEx)
#endif // DBG
#endif
NTSTATUS
IoCreateDisk(
IN PDEVICE_OBJECT DeviceObject,
IN PCREATE_DISK DiskInfo
)
/*++
Routine Description:
This routine creates an empty disk for the device object. It can operate
on either an EFI disk or an MBR disk. The parameters necessary to create
an empty disk vary for different type of partition tables the disks
contain.
Arguments:
DeviceObject - Device object to initialize disk for.
DiskInfo - The information necessary to create the disk. This will vary
for different partition types; e.g., MBR partitioned disks and
EFI partitioned disks. If DiskInfo is NULL, then we default
to initializing the disk to raw.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
ULONG PartitionStyle;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
//
// If DiskInfo is NULL, we default to RAW.
//
if ( DiskInfo == NULL ) {
PartitionStyle = PARTITION_STYLE_RAW;
} else {
PartitionStyle = DiskInfo->PartitionStyle;
}
//
// Call the lower level routine for EFI, MBR or RAW disks.
//
switch ( PartitionStyle ) {
case PARTITION_STYLE_GPT:
Status = FstubCreateDiskEFI ( DeviceObject, &DiskInfo->Gpt );
break;
case PARTITION_STYLE_MBR:
Status = FstubCreateDiskMBR ( DeviceObject, &DiskInfo->Mbr );
break;
case PARTITION_STYLE_RAW:
Status = FstubCreateDiskRaw ( DeviceObject );
break;
default:
Status = STATUS_NOT_SUPPORTED;
}
return Status;
}
NTSTATUS
IoWritePartitionTableEx(
IN PDEVICE_OBJECT DeviceObject,
IN PDRIVE_LAYOUT_INFORMATION_EX DriveLayout
)
/*++
Routine Description:
Write a partition table to the disk.
Arguments:
DeviceObject - The device object for the disk we want to write the
partition table for.
DriveLayout - The partition table information.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
ASSERT ( DriveLayout != NULL );
FstubDbgPrintDriveLayoutEx ( DriveLayout );
//
// Initialize a Disk structure.
//
Disk = NULL;
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if (!NT_SUCCESS (Status)) {
return Status;
}
//
// ISSUE - 2000/03/17 - math: Check partition type.
// We need to check the partition type so people don't write an MBR
// drive layout over a GPT partition table. Detect the partition style
// and if it doesn't match the one we're passed in, fail the call.
//
ASSERT ( Disk != NULL );
switch ( DriveLayout->PartitionStyle ) {
case PARTITION_STYLE_GPT: {
ULONG MaxPartitionCount;
PEFI_PARTITION_HEADER Header;
//
// Read the partition table header from the primary partition
// table.
//
Header = NULL;
//
// NB: Header is allocated in the disk's scratch buffer. Thus,
// it does not explicitly need to be deallocated.
//
Status = FstubReadHeaderEFI (
Disk,
PRIMARY_PARTITION_TABLE,
&Header
);
if (!NT_SUCCESS (Status)) {
//
// Failed reading the header from the primary partition table.
// Try the backup table.
//
Status = FstubReadHeaderEFI (
Disk,
BACKUP_PARTITION_TABLE,
&Header
);
if (!NT_SUCCESS (Status)) {
break;
}
}
MaxPartitionCount = Header->NumberOfEntries;
//
// You cannot write more partition table entries that the
// table will hold.
//
if (DriveLayout->PartitionCount > MaxPartitionCount) {
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: ERROR: Requested to write %d partitions\n"
"\tto a table that can hold a maximum of %d entries\n",
DriveLayout->PartitionCount,
MaxPartitionCount));
Status = STATUS_INVALID_PARAMETER;
break;
}
//
// Write the primary partition table.
//
Status = FstubWritePartitionTableEFI (
Disk,
DriveLayout->Gpt.DiskId,
MaxPartitionCount,
Header->FirstUsableLBA,
Header->LastUsableLBA,
PRIMARY_PARTITION_TABLE,
DriveLayout->PartitionCount,
DriveLayout->PartitionEntry
);
if (!NT_SUCCESS (Status)) {
break;
}
//
// Write the backup partition table.
//
Status = FstubWritePartitionTableEFI (
Disk,
DriveLayout->Gpt.DiskId,
MaxPartitionCount,
Header->FirstUsableLBA,
Header->LastUsableLBA,
BACKUP_PARTITION_TABLE,
DriveLayout->PartitionCount,
DriveLayout->PartitionEntry
);
break;
}
case PARTITION_STYLE_MBR:
Status = FstubWritePartitionTableMBR (
Disk,
DriveLayout
);
break;
default:
Status = STATUS_NOT_SUPPORTED;
}
if ( Disk != NULL ) {
FstubFreeDiskInformation ( Disk );
}
#if 0
//
// If we successfully wrote a new partition table. Verify that it is
// valid.
//
if ( NT_SUCCESS (Status)) {
NTSTATUS VerifyStatus;
VerifyStatus = IoVerifyPartitionTable ( DeviceObject, FALSE );
//
// STATUS_NOT_SUPPORTED is returned for MBR disks.
//
if (VerifyStatus != STATUS_NOT_SUPPORTED) {
ASSERT (NT_SUCCESS (VerifyStatus));
}
}
#endif
return Status;
}
NTSTATUS
IoReadPartitionTableEx(
IN PDEVICE_OBJECT DeviceObject,
IN PDRIVE_LAYOUT_INFORMATION_EX* DriveLayout
)
/*++
Routine Description:
This routine reads the partition table for the disk. Unlike
IoReadPartitionTable, this routine understands both EFI and MBR
partitioned disks.
The partition list is built in nonpaged pool that is allocated by this
routine. It is the caller's responsability to free this memory when it
is finished with the data.
Arguments:
DeviceObject - Pointer for device object for this disk.
DriveLayout - Pointer to the pointer that will return the patition list.
This buffer is allocated in nonpaged pool by this routine. It is
the responsability of the caller to free this memory if this
routine is successful.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PARTITION_STYLE Style;
BOOLEAN FoundGptPartition;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
ASSERT ( DriveLayout != NULL );
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if ( !NT_SUCCESS ( Status ) ) {
return Status;
}
ASSERT ( Disk != NULL );
Status = FstubDetectPartitionStyle (
Disk,
&Style
);
//
// To include oddities such as super-floppies, EZDrive disks and
// raw disks (which get a fake MBR partition created for them),
// we use the following algorithm:
//
// if ( valid gpt partition table)
// return GPT partition information
// else
// return MBR partition information
//
// When this code (especially FstubDetectPartitionStyle) is made
// to understand such things as super-floppies and raw disks, this
// will no longer be necessary.
//
FoundGptPartition = FALSE;
if ( NT_SUCCESS (Status) && Style == PARTITION_STYLE_GPT ) {
//
// First, read the primary partition table.
//
Status = FstubReadPartitionTableEFI (
Disk,
PRIMARY_PARTITION_TABLE,
DriveLayout
);
if (NT_SUCCESS (Status)) {
FoundGptPartition = TRUE;
} else {
//
// If the primary EFI partition table is invalid, try reading
// the backup partition table instead. We should find a way
// to notify the caller that the primary partition table is
// invalid so it can take the steps to fix it.
//
Status = FstubReadPartitionTableEFI (
Disk,
BACKUP_PARTITION_TABLE,
DriveLayout
);
if ( NT_SUCCESS (Status) ) {
FoundGptPartition = TRUE;
}
}
}
if ( !FoundGptPartition ) {
Status = FstubReadPartitionTableMBR (
Disk,
FALSE,
DriveLayout
);
}
if ( Disk ) {
FstubFreeDiskInformation ( Disk );
}
#if DBG
if (NT_SUCCESS (Status)) {
FstubDbgPrintDriveLayoutEx ( *DriveLayout );
}
#endif
return Status;
}
NTSTATUS
IoSetPartitionInformationEx(
IN PDEVICE_OBJECT DeviceObject,
IN ULONG PartitionNumber,
IN PSET_PARTITION_INFORMATION_EX PartitionInfo
)
/*++
Routine Description:
Set the partition information for a specific partition.
Arguments:
DeviceObject - Pointer to the device object for the disk.
PartitionNumber - A valid partition number we want to set the partition
information for.
PartitionInfo - The partition information.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PARTITION_STYLE Style;
ASSERT ( DeviceObject != NULL );
ASSERT ( PartitionInfo != NULL );
PAGED_CODE ();
//
// Initialization
//
Disk = NULL;
FstubDbgPrintSetPartitionEx (PartitionInfo, PartitionNumber);
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if (!NT_SUCCESS (Status)) {
return Status;
}
Status = FstubDetectPartitionStyle ( Disk, &Style );
if (!NT_SUCCESS (Status)) {
goto done;
}
if ( Style != PartitionInfo->PartitionStyle ) {
Status = STATUS_INVALID_PARAMETER;
goto done;
}
switch ( Style ) {
case PARTITION_STYLE_MBR:
Status = IoSetPartitionInformation (
DeviceObject,
Disk->SectorSize,
PartitionNumber,
PartitionInfo->Mbr.PartitionType
);
break;
case PARTITION_STYLE_GPT:
Status = FstubSetPartitionInformationEFI (
Disk,
PartitionNumber,
&PartitionInfo->Gpt
);
break;
default:
Status = STATUS_NOT_SUPPORTED;
}
done:
if ( Disk != NULL ) {
FstubFreeDiskInformation ( Disk );
Disk = NULL;
}
return Status;
}
NTSTATUS
IoUpdateDiskGeometry(
IN PDEVICE_OBJECT DeviceObject,
IN PDISK_GEOMETRY_EX OldDiskGeometry,
IN PDISK_GEOMETRY_EX NewDiskGeometry
)
/*++
Routine Description:
Update the disk geometry for the specific device. On an EFI disk the EFI
partition table will be moved to the end of the disk, so the final sectors
must be writable by the time this routine is called.
The primary and backup partition tables must be valid for this function to
succeed.
Arguments:
DeviceObject - The device whose geometry has changed.
OldDiskGeometry - The old disk geometry.
NewDiskGeometry - The new disk geometry.
Return Value:
NTSTATUS code
--*/
{
NTSTATUS Status;
PARTITION_STYLE Style;
PDISK_INFORMATION OldDisk;
PDISK_INFORMATION NewDisk;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
ASSERT ( OldDiskGeometry != NULL );
ASSERT ( NewDiskGeometry != NULL );
//
// Initialization.
//
OldDisk = NULL;
NewDisk = NULL;
//
// Allocate objects representing the old disk and the new disk.
//
Status = FstubAllocateDiskInformation (
DeviceObject,
&OldDisk,
(PINTERNAL_DISK_GEOMETRY) OldDiskGeometry
);
if ( !NT_SUCCESS (Status) ) {
goto done;
}
Status = FstubAllocateDiskInformation (
DeviceObject,
&NewDisk,
(PINTERNAL_DISK_GEOMETRY) NewDiskGeometry
);
if ( !NT_SUCCESS (Status) ) {
goto done;
}
Status = FstubDetectPartitionStyle (
OldDisk,
&Style
);
if ( !NT_SUCCESS (Status) ) {
goto done;
}
switch ( Style ) {
case PARTITION_STYLE_GPT:
//
// Update the geometry for an EFI disk.
//
Status = FstubUpdateDiskGeometryEFI (
OldDisk,
NewDisk
);
break;
case PARTITION_STYLE_MBR:
//
// For MBR partitioned drives, there is nothing to do, so
// we succeed by default.
//
Status = STATUS_SUCCESS;
break;
default:
Status = STATUS_NOT_SUPPORTED;
}
done:
if ( OldDisk ) {
FstubFreeDiskInformation ( OldDisk );
OldDisk = NULL;
}
if ( NewDisk ) {
FstubFreeDiskInformation ( NewDisk );
NewDisk = NULL;
}
return Status;
}
NTSTATUS
IoReadDiskSignature(
IN PDEVICE_OBJECT DeviceObject,
IN ULONG BytesPerSector,
OUT PDISK_SIGNATURE Signature
)
/*++
Routine Description:
This routine will read the disk signature information from the disk. For
MBR disks, it will read the disk signature and calculate a checksum of the
contents of the MBR. For GPT disks, it will obtain the EFI DiskId from
the disk.
Arguments:
DeviceObject - A disk device object.
BytesPerSector - The number of bytes per sector on this disk.
DiskSignature - A buffer where the disk information will be stored.
Return Value:
NT Status code.
--*/
{
NTSTATUS Status;
PULONG Mbr;
PAGED_CODE();
//
// Make sure sector size is at least 512 bytes.
//
if (BytesPerSector < 512) {
BytesPerSector = 512;
}
//
// Allocate buffer for sector read.
//
Mbr = ExAllocatePoolWithTag(NonPagedPoolCacheAligned,
BytesPerSector,
FSTUB_TAG);
if (Mbr == NULL) {
return STATUS_NO_MEMORY;
}
Status = FstubReadSector (
DeviceObject,
BytesPerSector,
0,
Mbr);
if (!NT_SUCCESS (Status)) {
ExFreePool (Mbr);
return Status;
}
//
// If this is an EFI disk get the EFI disk signature instead.
//
if ( ((MASTER_BOOT_RECORD*)Mbr)->Partition[0].OSIndicator == EFI_MBR_PARTITION_TYPE &&
((MASTER_BOOT_RECORD*)Mbr)->Partition[1].OSIndicator == 0 &&
((MASTER_BOOT_RECORD*)Mbr)->Partition[2].OSIndicator == 0 &&
((MASTER_BOOT_RECORD*)Mbr)->Partition[3].OSIndicator == 0 ) {
PEFI_PARTITION_HEADER EfiHeader;
ULONG32 Temp;
ULONG32 CheckSum;
//
// Get the EFI disk guid.
//
Status = FstubReadSector (
DeviceObject,
BytesPerSector,
1,
Mbr);
if (!NT_SUCCESS (Status)) {
ExFreePool (Mbr);
return Status;
}
EfiHeader = (PEFI_PARTITION_HEADER) Mbr;
//
// Compute the CRC32 CheckSum of the header block. This is used to
// verify that we have a valid EFI disk.
//
Temp = EfiHeader->HeaderCRC32;
EfiHeader->HeaderCRC32 = 0;
CheckSum = RtlComputeCrc32 (0, EfiHeader, EfiHeader->HeaderSize);
EfiHeader->HeaderCRC32 = Temp;
//
// The EFI CheckSum doesn't match what was in it's header. Return
// failure.
//
if (CheckSum != EfiHeader->HeaderCRC32) {
ExFreePool (Mbr);
return STATUS_DISK_CORRUPT_ERROR;
}
//
// This is a valid EFI disk. Copy the disk signature from the
// EFI Header sector.
//
Signature->PartitionStyle = PARTITION_STYLE_GPT;
Signature->Gpt.DiskId = EfiHeader->DiskGUID;
} else {
ULONG i;
ULONG MbrCheckSum;
//
// Calculate MBR checksum.
//
MbrCheckSum = 0;
for (i = 0; i < 128; i++) {
MbrCheckSum += Mbr[i];
}
MbrCheckSum = ~(MbrCheckSum) + 1;
//
// Get the signature out of the sector and save it in the disk data block.
//
Signature->PartitionStyle = PARTITION_STYLE_MBR;
Signature->Mbr.Signature = Mbr [PARTITION_TABLE_OFFSET/2-1];
Signature->Mbr.CheckSum = MbrCheckSum;
}
ExFreePool (Mbr);
return Status;
}
NTSTATUS
IoVerifyPartitionTable(
IN PDEVICE_OBJECT DeviceObject,
IN BOOLEAN FixErrors
)
/*++
Routine Description:
Verify that the partition table and backup partition table (if present)
is valid. If these tables are NOT valid, and FixErrors is TRUE, and the
errors are recoverable errors, fix them.
Arguments:
DeviceObject - A disk whose partition table should be verified and/or
fixed.
FixErrors - If the partition table contains errors and these errors are
recoverable errors, fix the errors. Otherwise, the disk will not
be modified.
Return Value:
STATUS_SUCCESS - If the final partition table, after any modifications
done by this routine, is valid.
STATUS_DISK_CORRUPT_ERROR - If the final partition table, after any
modifications done by this routine, is not valid.
Other NTSTATUS code - Some other failure.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PARTITION_STYLE Style;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if ( !NT_SUCCESS ( Status ) ) {
return Status;
}
ASSERT ( Disk != NULL );
Status = FstubDetectPartitionStyle (
Disk,
&Style
);
if ( !NT_SUCCESS (Status) ) {
FstubFreeDiskInformation ( Disk );
Disk = NULL;
return Status;
}
switch ( Style ) {
case PARTITION_STYLE_GPT:
Status = FstubVerifyPartitionTableEFI (
Disk,
FixErrors
);
break;
case PARTITION_STYLE_MBR:
Status = STATUS_SUCCESS;
break;
default:
Status = STATUS_NOT_SUPPORTED;
}
if ( Disk ) {
FstubFreeDiskInformation ( Disk );
}
return Status;
}
//
// Internal Routines
//
NTSTATUS
FstubSetPartitionInformationEFI(
IN PDISK_INFORMATION Disk,
IN ULONG PartitionNumber,
IN SET_PARTITION_INFORMATION_GPT* PartitionInfo
)
/*++
Routine Description:
Update the partition information for a specific EFI partition.
The algorithm we use reads the entire partition table and writes it back
again. This makes sense, because the entire table will have to be read in
ANYWAY, since we have to checksum the table.
NB: this algorithm assumes that the partition table hasn't changed since
the time GetDriveLayout was called. Probably a safe assumption.
Arguments:
Disk -
PartitionNumber -
PartitionInfo -
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PPARTITION_INFORMATION_GPT EntryInfo;
PDRIVE_LAYOUT_INFORMATION_EX Layout;
ULONG PartitionOrdinal;
ASSERT ( Disk != NULL );
ASSERT ( PartitionInfo != NULL );
PAGED_CODE ();
//
// Initialization
//
Layout = NULL;
if ( PartitionNumber == 0 ) {
return STATUS_INVALID_PARAMETER;
}
PartitionOrdinal = PartitionNumber - 1;
//
// Read in the entire partition table.
//
Status = IoReadPartitionTableEx (
Disk->DeviceObject,
&Layout
);
if (!NT_SUCCESS (Status)) {
return Status;
}
ASSERT ( Layout != NULL );
//
// If it's out of range, fail.
//
if ( PartitionOrdinal >= Layout->PartitionCount ) {
ExFreePool ( Layout );
return STATUS_INVALID_PARAMETER;
}
//
// Copy the information into the partition array.
//
EntryInfo = &Layout->PartitionEntry [PartitionOrdinal].Gpt;
EntryInfo->PartitionType = PartitionInfo->PartitionType;
EntryInfo->PartitionId = PartitionInfo->PartitionId;
EntryInfo->Attributes = PartitionInfo->Attributes;
RtlCopyMemory (
EntryInfo->Name,
PartitionInfo->Name,
sizeof (EntryInfo->Name)
);
//
// And rewrite the partition table.
//
Status = IoWritePartitionTableEx (
Disk->DeviceObject,
Layout
);
ExFreePool ( Layout );
Layout = NULL;
return Status;
}
NTSTATUS
FstubReadPartitionTableMBR(
IN PDISK_INFORMATION Disk,
IN BOOLEAN RecognizedPartitionsOnly,
OUT PDRIVE_LAYOUT_INFORMATION_EX* ReturnedDriveLayout
)
/*++
Routine Description:
Read the MBR partition table.
Arguments:
Disk - The disk we want to obtain the partition information for.
RecognizedPartitionsOnly - Whether to return information for all
partitions or only recognized partitions.
ReturnedDriveLayout - A pointer to pointer where the drive layout
information will be returned. The caller of this function is
responsible for freeing this memory using ExFreePool.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
ULONG i;
ULONG Size;
PDRIVE_LAYOUT_INFORMATION Layout;
PDRIVE_LAYOUT_INFORMATION_EX LayoutEx;
PPARTITION_INFORMATION Entry;
PPARTITION_INFORMATION_EX EntryEx;
PAGED_CODE ();
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
ASSERT ( ReturnedDriveLayout != NULL );
//
// Initialization
//
*ReturnedDriveLayout = NULL;
Layout = NULL;
Status = IoReadPartitionTable (
Disk->DeviceObject,
Disk->SectorSize,
RecognizedPartitionsOnly,
&Layout
);
if (!NT_SUCCESS (Status)) {
return Status;
}
Size = FIELD_OFFSET (DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[0]) +
Layout->PartitionCount * sizeof (PARTITION_INFORMATION_EX);
LayoutEx = ExAllocatePoolWithTag (
NonPagedPool,
Size,
FSTUB_TAG
);
if ( LayoutEx == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Tranlated the drive layout information to the extended drive layout
// information.
//
LayoutEx->PartitionStyle = PARTITION_STYLE_MBR;
LayoutEx->PartitionCount = Layout->PartitionCount;
LayoutEx->Mbr.Signature = Layout->Signature;
//
// Translate each entry in the table.
//
for (i = 0; i < Layout->PartitionCount; i++) {
EntryEx = &LayoutEx->PartitionEntry[i];
Entry = &Layout->PartitionEntry[i];
EntryEx->PartitionStyle = PARTITION_STYLE_MBR;
EntryEx->StartingOffset = Entry->StartingOffset;
EntryEx->PartitionLength = Entry->PartitionLength;
EntryEx->RewritePartition = Entry->RewritePartition;
EntryEx->PartitionNumber = Entry->PartitionNumber;
EntryEx->Mbr.PartitionType = Entry->PartitionType;
EntryEx->Mbr.BootIndicator = Entry->BootIndicator;
EntryEx->Mbr.RecognizedPartition = Entry->RecognizedPartition;
EntryEx->Mbr.HiddenSectors = Entry->HiddenSectors;
}
//
// Free layout information allocated by IoReadPartitionTable.
//
ExFreePool ( Layout );
//
// And return the translated, EX information.
//
*ReturnedDriveLayout = LayoutEx;
return Status;
}
NTSTATUS
FstubDetectPartitionStyle(
IN PDISK_INFORMATION Disk,
OUT PARTITION_STYLE* PartitionStyle
)
/*++
Routine Description:
Detect how a disk has been partitioned. For an MBR partitioned disk,
sector zero contains the MBR signature. For an EFI partitioned disk,
sector zero contains a legacy style MBR with a single partition that
consumes the entire disk.
Arguments:
Disk - The disk to determine the partition style for.
PartitionStyle - A buffer to
Return Values:
STATUS_SUCCESS - If the disk has been partitioned by a recognized
partitioning scheme EFI or MBR.
STATUS_UNSUCCESSFUL - If partitioning scheme was not recognized.
--*/
{
NTSTATUS Status;
MASTER_BOOT_RECORD* Mbr;
PAGED_CODE ();
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
ASSERT ( PartitionStyle != NULL );
//
// Read sector 0. This will contan the mbr on an mbr-partition disk
// or the legacy mbr on an efi-partitioned disk.
//
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
0,
Disk->ScratchBuffer
);
if ( !NT_SUCCESS ( Status ) ) {
return Status;
}
Mbr = Disk->ScratchBuffer;
//
// If the disk has an MBR
//
*PartitionStyle = -1;
if (Mbr->Signature == MBR_SIGNATURE) {
if (Mbr->Partition[0].OSIndicator == EFI_MBR_PARTITION_TYPE &&
Mbr->Partition[1].OSIndicator == 0 &&
Mbr->Partition[2].OSIndicator == 0 &&
Mbr->Partition[3].OSIndicator == 0) {
*PartitionStyle = PARTITION_STYLE_GPT;
Status = STATUS_SUCCESS;
} else {
*PartitionStyle = PARTITION_STYLE_MBR;
Status = STATUS_SUCCESS;
}
} else {
Status = STATUS_UNSUCCESSFUL;
}
return Status;
}
NTSTATUS
FstubGetDiskGeometry(
IN PDEVICE_OBJECT DeviceObject,
IN PINTERNAL_DISK_GEOMETRY Geometry
)
/*++
Routine Description:
We need this routine to get the number of cylinders that the disk driver
thinks is on the drive. We will need this to calculate CHS values
when we fill in the partition table entries.
Arguments:
DeviceObject - The device object describing the entire drive.
Return Value:
None.
--*/
{
PIRP localIrp;
PINTERNAL_DISK_GEOMETRY diskGeometry;
PIO_STATUS_BLOCK iosb;
PKEVENT eventPtr;
NTSTATUS status;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
ASSERT ( Geometry != NULL );
//
// Initialization
//
eventPtr = NULL;
iosb = NULL;
diskGeometry = NULL;
diskGeometry = ExAllocatePoolWithTag(
NonPagedPool,
sizeof (*diskGeometry),
'btsF'
);
if (!diskGeometry) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
iosb = ExAllocatePoolWithTag(
NonPagedPool,
sizeof(IO_STATUS_BLOCK),
'btsF'
);
if (!iosb) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
eventPtr = ExAllocatePoolWithTag(
NonPagedPool,
sizeof(KEVENT),
'btsF'
);
if (!eventPtr) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
KeInitializeEvent(
eventPtr,
NotificationEvent,
FALSE
);
localIrp = IoBuildDeviceIoControlRequest(
IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
DeviceObject,
NULL,
0UL,
diskGeometry,
sizeof (*diskGeometry),
FALSE,
eventPtr,
iosb
);
if (!localIrp) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
//
// Call the lower level driver, wait for the opertion
// to finish.
//
status = IoCallDriver(
DeviceObject,
localIrp
);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(
eventPtr,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER) NULL
);
status = iosb->Status;
}
if (NT_SUCCESS(status)) {
RtlCopyMemory (Geometry, diskGeometry, sizeof (*Geometry));
}
done:
if ( eventPtr ) {
ExFreePool (eventPtr);
eventPtr = NULL;
}
if ( iosb ) {
ExFreePool(iosb);
iosb = NULL;
}
if ( diskGeometry ) {
ExFreePool (diskGeometry);
diskGeometry = NULL;
}
if ( NT_SUCCESS ( status ) ) {
//
// If the the partition entry size is not a factor of the disk block
// size, we will need to add code to deal with partition entries that
// span physical sectors. This may happen if you change the size of
// the partition entry or if you have a disk with a block size less
// than 128 bytes.
//
ASSERT ( (Geometry->Geometry.BytesPerSector % PARTITION_ENTRY_SIZE) == 0);
}
return status;
}
NTSTATUS
FstubAllocateDiskInformation(
IN PDEVICE_OBJECT DeviceObject,
OUT PDISK_INFORMATION * DiskBuffer,
IN PINTERNAL_DISK_GEOMETRY Geometry OPTIONAL
)
/*++
Routine Description:
Allocate and initialize a DISK_INFORMATION structure describing the
disk DeviceObject.
Arguments:
DeviceObject - A device object describing the entire disk.
DiskBuffer - A buffer to a recieve the allocated DISK_INFORMATION pointer.
Geometry - An optional pointer to an INTERNAL_DISK_GEOMETRY structure. If
this pointer is NULL, the disk will be querried for it's geometry
using IOCTL_DISK_GET_DRIVE_GEOMETRY_EX.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PVOID Buffer;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
ASSERT ( DiskBuffer != NULL );
Disk = ExAllocatePoolWithTag (
NonPagedPool,
sizeof (DISK_INFORMATION),
FSTUB_TAG
);
if ( Disk == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
if ( Geometry ) {
RtlCopyMemory (
&Disk->Geometry,
Geometry,
sizeof (Disk->Geometry)
);
} else {
Status = FstubGetDiskGeometry (
DeviceObject,
&Disk->Geometry
);
if (!NT_SUCCESS (Status)) {
KdPrintEx ((DPFLTR_FSTUB_ID,
DPFLTR_ERROR_LEVEL,
"FSTUB: disk %p failed to report geometry.\n",
DeviceObject));
ExFreePool ( Disk );
return Status;
}
}
//
// Check the geometry. Sometimes drives report incorrect geometry.
// Removable drives without media report a size of zero.
//
if (Disk->Geometry.Geometry.BytesPerSector == 0 ||
Disk->Geometry.DiskSize.QuadPart == 0) {
KdPrintEx ((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: disk %p reported invalid geometry. Probably a removable.\n"
" SectorSize %d\n"
" DiskSize %I64x\n",
DeviceObject,
Disk->Geometry.Geometry.BytesPerSector,
Disk->Geometry.DiskSize.QuadPart));
ExFreePool ( Disk );
return STATUS_DEVICE_NOT_READY;
}
Disk->DeviceObject = DeviceObject;
Disk->SectorSize = Disk->Geometry.Geometry.BytesPerSector;
//
// Do not use sector-count = cylinders * tracks * sector-size. Devices
// like the memory stick can report a correct disk size and a more or
// less correct sector size, a completely invalid number of cylinders
// or tracks. Since the only thing we really need here is the sector
// count, avoid using these potentially incorrect values.
//
Disk->SectorCount = Disk->Geometry.DiskSize.QuadPart /
(ULONGLONG) Disk->Geometry.Geometry.BytesPerSector;
//
// NOTE: This does not need to be nonpaged or cache aligned, does it?
//
Buffer = ExAllocatePoolWithTag (
NonPagedPoolCacheAligned,
Disk->SectorSize,
FSTUB_TAG
);
if ( Buffer == NULL ) {
ExFreePool ( Disk );
return STATUS_INSUFFICIENT_RESOURCES;
}
Disk->ScratchBuffer = Buffer;
*DiskBuffer = Disk;
return STATUS_SUCCESS;
}
NTSTATUS
FstubFreeDiskInformation(
IN OUT PDISK_INFORMATION Disk
)
/*++
Routine Description:
Free the allocated disk information.
Arguments:
Disk - Disk information previously allocated
with FstubAllocateDiskInformation().
Return Value:
NTSTATUS code.
--*/
{
//
// Free up disk scratch buffer and disk object.
//
if ( Disk && Disk->ScratchBuffer ) {
ExFreePool (Disk->ScratchBuffer);
Disk->ScratchBuffer = NULL;
}
if ( Disk ) {
ExFreePool (Disk);
Disk = NULL;
}
return STATUS_SUCCESS;
}
NTSTATUS
FstubWriteBootSectorEFI(
IN CONST PDISK_INFORMATION Disk
)
/*++
Routine Description:
Write the boot sector for an EFI partitioned disk. Note that the EFI
boot sector uses the structure as the legacy AT-style MBR, but it
contains only one partition entry and that entry covers the entire disk.
Arguments:
Disk - The disk to write the MBR for.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
MASTER_BOOT_RECORD* Mbr;
PAGED_CODE ();
ASSERT ( Disk );
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
//
// Construct an EFI Master Boot Record. The EFI Master Boot Record has
// one partition entry which is setup to consume the entire disk. The
// MBR we are writing is configured to boot using only the EFI firmware.
// It will not via the legacy BIOS because we do not write valid
// instructions to it.
//
Mbr = Disk->ScratchBuffer;
//
// The rest of this sector is not accessed by EFI. Zero it out so
// other tools do not get confused. Especially, we need to make sure
// the NTFT signature is NULL.
//
RtlZeroMemory (Mbr, Disk->SectorSize);
//
// NB: the cylinder and head values are 0-based, but the sector
// value is 1-based.
//
//
// ISSUE - 2000/02/01 - math: Is it necessary to properly initialize the
// Head, Track, Sector and SizeInLba field for legacy BIOS compatability?
// We are not doing this in the diskpart program, so probably not.
//
Mbr->Signature = MBR_SIGNATURE;
Mbr->Partition[0].BootIndicator = 0;
Mbr->Partition[0].StartHead = 0;
Mbr->Partition[0].StartSector = 2;
Mbr->Partition[0].StartTrack = 0;
Mbr->Partition[0].OSIndicator = EFI_MBR_PARTITION_TYPE;
Mbr->Partition[0].EndHead = 0xFF;
Mbr->Partition[0].EndSector = 0xFF;
Mbr->Partition[0].EndTrack = 0xFF;
Mbr->Partition[0].StartingLBA = 1;
Mbr->Partition[0].SizeInLBA = 0xFFFFFFFF;
//
// Zero out the remaining partitions as per the EFI spec.
//
RtlZeroMemory (&Mbr->Partition[1], sizeof (Mbr->Partition[1]));
RtlZeroMemory (&Mbr->Partition[2], sizeof (Mbr->Partition[2]));
RtlZeroMemory (&Mbr->Partition[3], sizeof (Mbr->Partition[3]));
//
// Write the EFI MBR to the zeroth sector of the disk.
//
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
0,
Mbr
);
return Status;
}
PDRIVE_LAYOUT_INFORMATION
FstubConvertExtendedToLayout(
IN PDRIVE_LAYOUT_INFORMATION_EX LayoutEx
)
/*++
Routine Description:
Convert an extended drive layout structure to a (old) drive
layout structure. Necessarily, the LayoutEx structure must
represent an MBR layout, not a GPT layout.
Arguments:
LayoutEx - The extended drive layout structure to be converted.
Return Value:
The converted drive layout structure.
--*/
{
ULONG i;
ULONG LayoutSize;
PDRIVE_LAYOUT_INFORMATION Layout;
PPARTITION_INFORMATION Partition;
PPARTITION_INFORMATION_EX PartitionEx;
PAGED_CODE ();
ASSERT ( LayoutEx );
//
// The only valid conversion is from an MBR extended layout structure to
// the old structure.
//
if (LayoutEx->PartitionStyle != PARTITION_STYLE_MBR) {
ASSERT ( FALSE );
return NULL;
}
LayoutSize = FIELD_OFFSET (DRIVE_LAYOUT_INFORMATION, PartitionEntry[0]) +
LayoutEx->PartitionCount * sizeof (PARTITION_INFORMATION);
Layout = ExAllocatePoolWithTag (
NonPagedPool,
LayoutSize,
FSTUB_TAG
);
if ( Layout == NULL ) {
return NULL;
}
Layout->Signature = LayoutEx->Mbr.Signature;
Layout->PartitionCount = LayoutEx->PartitionCount;
for (i = 0; i < LayoutEx->PartitionCount; i++) {
Partition = &Layout->PartitionEntry[i];
PartitionEx = &LayoutEx->PartitionEntry[i];
Partition->StartingOffset = PartitionEx->StartingOffset;
Partition->PartitionLength = PartitionEx->PartitionLength;
Partition->RewritePartition = PartitionEx->RewritePartition;
Partition->PartitionNumber = PartitionEx->PartitionNumber;
Partition->PartitionType = PartitionEx->Mbr.PartitionType;
Partition->BootIndicator = PartitionEx->Mbr.BootIndicator;
Partition->RecognizedPartition = PartitionEx->Mbr.RecognizedPartition;
Partition->HiddenSectors = PartitionEx->Mbr.HiddenSectors;
}
return Layout;
}
NTSTATUS
FstubWritePartitionTableMBR(
IN PDISK_INFORMATION Disk,
IN PDRIVE_LAYOUT_INFORMATION_EX LayoutEx
)
/*++
Routine Description:
Write the MBR partition table represented by LayoutEx to
the disk.
Arguments:
Disk - The disk where the partition table should be written.
LayoutEx - The new layout information.
Return Value:
NTSTATUS code
--*/
{
NTSTATUS Status;
PDRIVE_LAYOUT_INFORMATION Layout;
PAGED_CODE ();
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
ASSERT ( LayoutEx != NULL );
//
// Convert extended layout structure to old layout structure.
//
Layout = FstubConvertExtendedToLayout ( LayoutEx );
if ( Layout == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = IoWritePartitionTable (
Disk->DeviceObject,
Disk->SectorSize,
Disk->Geometry.Geometry.SectorsPerTrack,
Disk->Geometry.Geometry.TracksPerCylinder,
Layout
);
return Status;
}
NTSTATUS
FstubWriteEntryEFI(
IN PDISK_INFORMATION Disk,
IN ULONG PartitionEntrySectorCount,
IN ULONG EntryNumber,
IN PEFI_PARTITION_ENTRY PartitionEntry,
IN ULONG Partition,
IN BOOLEAN Flush,
IN OUT ULONG32* PartialCheckSum
)
/*++
Routine Description:
Write an EFI partition entry to the EFI partition table for this disk.
The partition table writes are buffered until an entire disk block's worth
of entries have been written, then written to the disk.
Arguments:
Disk - The disk to write the partition entry for.
PartitionEntrySectorCount - The count of blocks that the partition table
occupies.
EntryNumber - The index into the partition table array to write this
entry.
PartitionEntry - The partition entry data.
Partition - Whether this is the main partition table or the backup
partition table.
Flush - Boolean to force the flushing of the table now (TRUE) or wait
until a complete block's worth of data is ready to be written
(FALSE).
PartialCheckSum - The updated partial checksum including this entry.
Return Values:
NTSTATUS code.
--*/
{
ULONG Offset;
ULONGLONG Lba;
ULONGLONG StartOfEntryArray;
NTSTATUS Status;
PAGED_CODE ();
ASSERT ( Disk );
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
//
// The primary partition table begins after the EFI Master Boot Record
// (block 0) and the EFI partition table header (block 1). The backup
// partition table ends at the last block of the disk, hence it begins
// in (from the end) as many blocks as it ocupies on the disk.
//
if ( Partition == PRIMARY_PARTITION_TABLE ) {
StartOfEntryArray = 2;
} else {
StartOfEntryArray = Disk->SectorCount - PartitionEntrySectorCount - 1;
}
Lba = ( EntryNumber * PARTITION_ENTRY_SIZE ) / Disk->SectorSize;
Offset = ( EntryNumber * PARTITION_ENTRY_SIZE ) % Disk->SectorSize;
RtlCopyMemory (
((PUCHAR) Disk->ScratchBuffer) + Offset,
PartitionEntry,
PARTITION_ENTRY_SIZE
);
Offset += PARTITION_ENTRY_SIZE;
ASSERT ( Offset <= Disk->SectorSize );
//
// Flush the buffer if necessary.
//
if ( Offset == Disk->SectorSize || Flush ) {
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
StartOfEntryArray + Lba,
Disk->ScratchBuffer
);
if (!NT_SUCCESS (Status)) {
return Status;
}
RtlZeroMemory ( Disk->ScratchBuffer, Disk->SectorSize );
}
if ( PartialCheckSum ) {
*PartialCheckSum = RtlComputeCrc32 (
*PartialCheckSum,
PartitionEntry,
PARTITION_ENTRY_SIZE
);
}
return STATUS_SUCCESS;
}
NTSTATUS
FstubWriteHeaderEFI(
IN PDISK_INFORMATION Disk,
IN ULONG PartitionEntrySectorCount,
IN GUID DiskGUID,
IN ULONG32 MaxPartitionCount,
IN ULONG64 FirstUsableLBA,
IN ULONG64 LastUsableLBA,
IN ULONG32 CheckSum,
IN ULONG Partition
)
/*++
Routine Description:
Write an EFI partition table header to the disk.
Arguments:
Disk - The disk the partition table header should be written to.
PartitionEntrySectorCount - The number of sectors that the partition
table array occupies. These must be complete sectors.
DiskGUID - The Unique GUID for this disk.
MaxPartitionCount - The maximum number of partitions allowed for this
disk.
FirstUsableLBA - The beginning sector of partitionable space for this
disk. This value must be larger than the space consumed by the
MBR, and partition table. This value is never validated for
correctness.
LastUsableLBA - The last sector of partitionable space on this disk. This
value must be smaller than the last disk sector less space
necessary for the backup partition table. This value is not
validated for correctness.
CheckSum - The CRC32 checksum for the partition entry array.
Partition - Which partition we are writing, the primary partition or
the backup partition.
Return Values:
NTSTATUS code.
Notes:
PartitionEntrySectorCount could be derived from MaxPartitionCount.
--*/
{
NTSTATUS Status;
PEFI_PARTITION_HEADER TableHeader;
ULONG32 HeaderCheckSum;
ASSERT ( Disk != NULL );
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
PAGED_CODE ();
TableHeader = Disk->ScratchBuffer;
TableHeader->Signature = EFI_PARTITION_TABLE_SIGNATURE;
TableHeader->Revision = EFI_PARTITION_TABLE_REVISION;
TableHeader->HeaderSize = sizeof (EFI_PARTITION_HEADER);
TableHeader->HeaderCRC32 = 0;
TableHeader->Reserved = 0;
//
// The primary partition table starts at block 1. The backup partition
// table ends at the end of the disk.
//
if ( Partition == PRIMARY_PARTITION_TABLE ) {
TableHeader->MyLBA = 1;
TableHeader->AlternateLBA = Disk->SectorCount - 1;
} else {
TableHeader->MyLBA = Disk->SectorCount - 1;
TableHeader->AlternateLBA = 1;
}
TableHeader->FirstUsableLBA = FirstUsableLBA;
TableHeader->LastUsableLBA = LastUsableLBA;
TableHeader->DiskGUID = DiskGUID;
TableHeader->NumberOfEntries = MaxPartitionCount;
TableHeader->SizeOfPartitionEntry = PARTITION_ENTRY_SIZE;
TableHeader->PartitionEntryCRC32 = CheckSum;
//
// For the primary partition table the partition entry array begins the
// sector directly following the partition table header sector. For the
// backup partition table, the partition table header sector directly
// follows the partition entry array. The partition table header for
// a backup partition is located on the last sector of the disk.
//
if ( Partition == PRIMARY_PARTITION_TABLE ) {
TableHeader->PartitionEntryLBA = TableHeader->MyLBA + 1;
} else {
TableHeader->PartitionEntryLBA = TableHeader->MyLBA - PartitionEntrySectorCount;
}
TableHeader->HeaderCRC32 = RtlComputeCrc32 (
0,
TableHeader,
TableHeader->HeaderSize
);
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: Dump of %s EFI partition table\n"
" Signature: %I64x\n"
" Revision: %x\n"
" HeaderSize: %x\n"
" HeaderCRC32: %x\n"
" MyLBA: %I64x\n"
" AlternateLBA: %I64x\n",
(Partition == PRIMARY_PARTITION_TABLE) ? "Primary" : "Backup",
TableHeader->Signature,
TableHeader->Revision,
TableHeader->HeaderSize,
TableHeader->HeaderCRC32,
TableHeader->MyLBA,
TableHeader->AlternateLBA));
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
" FirstUsableLBA: %I64x\n"
" LastUsableLBA: %I64x\n"
" NumberOfEntries: %x\n"
" SizeOfPartitionEntry: %x\n"
" PartitionEntryCRC32: %x\n\n",
TableHeader->FirstUsableLBA,
TableHeader->LastUsableLBA,
TableHeader->NumberOfEntries,
TableHeader->SizeOfPartitionEntry,
TableHeader->PartitionEntryCRC32));
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
TableHeader->MyLBA,
TableHeader
);
return Status;
}
VOID
FstubAdjustPartitionCount(
IN ULONG SectorSize,
IN OUT PULONG PartitionCount
)
/*++
Routine Description:
Adjust the PartitionCount to be a valid EFI Maximum Partition Count.
A valid value for the partition must be larger than MIN_PARTITOIN_COUNT,
currently 128, and adjusted to take up as much of the remaining disk
sector as is possible.
Arguments:
SectorSize - The disk sector size.
PartitionCount - The count to be adjusted.
Return Values:
None.
--*/
{
ULONG Count;
ULONG EntrySize;
PAGED_CODE ();
ASSERT ( SectorSize != 0 );
ASSERT ( PartitionCount != NULL );
EntrySize = PARTITION_ENTRY_SIZE;
Count = max (*PartitionCount, MIN_PARTITION_COUNT);
Count = ROUND_TO ( EntrySize * Count, SectorSize ) / EntrySize;
ASSERT ( *PartitionCount <= Count );
*PartitionCount = Count;
#if DBG
//
// If we're on a machine with a 512 byte block (nearly every machine),
// verify that we've calculated a reasonable Count.
//
if (SectorSize == 512) {
ASSERT ( Count % 4 == 0 );
}
#endif
}
NTSTATUS
FstubCreateDiskEFI(
IN PDEVICE_OBJECT DeviceObject,
IN PCREATE_DISK_GPT DiskInfo
)
/*++
Routine Description:
Lay down an empty EFI partition table on a virgin disk.
Arguments:
DeviceObject - The device object describing the drive.
Layout - The EFI disk layout information.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
ULONG64 FirstUsableLBA;
ULONG64 LastUsableLBA;
ULONG64 PartitionBlocks;
ULONG32 MaxPartitionCount;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
ASSERT ( DiskInfo != NULL );
//
// Initialization
//
Disk = NULL;
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if (!NT_SUCCESS (Status)) {
return Status;
}
ASSERT ( Disk != NULL );
//
// Write the EFI MBR to the disk.
//
Status = FstubWriteBootSectorEFI ( Disk );
if ( !NT_SUCCESS (Status) ) {
FstubFreeDiskInformation ( Disk );
Disk = NULL;
return Status;
}
MaxPartitionCount = DiskInfo->MaxPartitionCount;
FstubAdjustPartitionCount (
Disk->SectorSize,
&MaxPartitionCount
);
//
// Initialize the start of partitionable space and the length of
// partitionable space on this drive.
//
PartitionBlocks = ( MaxPartitionCount * PARTITION_ENTRY_SIZE ) / Disk->SectorSize;
FirstUsableLBA = PartitionBlocks + 2;
LastUsableLBA = Disk->SectorCount - ( PartitionBlocks + 1 );
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_TRACE_LEVEL,
"FSTUB: Disk Information\n"
" SectorCount: %I64x\n\n",
Disk->SectorCount));
//
// Write the primary partition table.
//
Status = FstubWritePartitionTableEFI (
Disk,
DiskInfo->DiskId,
MaxPartitionCount,
FirstUsableLBA,
LastUsableLBA,
PRIMARY_PARTITION_TABLE,
0,
NULL
);
if (NT_SUCCESS (Status)) {
//
// Write the backup partition table.
//
Status = FstubWritePartitionTableEFI (
Disk,
DiskInfo->DiskId,
MaxPartitionCount,
FirstUsableLBA,
LastUsableLBA,
BACKUP_PARTITION_TABLE,
0,
NULL
);
}
FstubFreeDiskInformation ( Disk );
return Status;
}
NTSTATUS
FstubCreateDiskMBR(
IN PDEVICE_OBJECT DeviceObject,
IN PCREATE_DISK_MBR DiskInfo
)
/*++
Routine Description:
Create an empty MBR partition table on the disk. Note
that when creating an empty MBR disk, we do not overwrite
the bootstrapping code at the beginning of the MBR.
Arguments:
DeviceObject - The device that should
Return Value:
NTSTATUS code
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PMASTER_BOOT_RECORD Mbr;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
//
// Initialization
//
Disk = NULL;
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if (!NT_SUCCESS (Status)) {
return Status;
}
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
0,
Disk->ScratchBuffer
);
if ( !NT_SUCCESS ( Status ) ) {
goto done;
}
Mbr = (PMASTER_BOOT_RECORD) Disk->ScratchBuffer;
//
// Zero out all partition entries, set the AA55 signature
// and set the NTFT signature.
//
RtlZeroMemory (&Mbr->Partition, sizeof (Mbr->Partition));
Mbr->Signature = MBR_SIGNATURE;
Mbr->DiskSignature = DiskInfo->Signature;
//
// Then write the sector back to the drive.
//
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
0,
Mbr
);
done:
if ( Disk ) {
FstubFreeDiskInformation ( Disk );
Disk = NULL;
}
return Status;
}
NTSTATUS
FstubCreateDiskRaw(
IN PDEVICE_OBJECT DeviceObject
)
/*++
Routine Description:
Erase all partition information from the disk.
Arguments:
DeviceObject - Device object representing a disk to remove
partition table from.
Return Value:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PDISK_INFORMATION Disk;
PMASTER_BOOT_RECORD Mbr;
PARTITION_STYLE PartitionStyle;
PAGED_CODE ();
ASSERT ( DeviceObject != NULL );
//
// Initialization
//
Disk = NULL;
Status = FstubAllocateDiskInformation (
DeviceObject,
&Disk,
NULL
);
if (!NT_SUCCESS (Status)) {
return Status;
}
//
// Figure out whether this is an MBR or GPT disk.
//
Status = FstubDetectPartitionStyle (
Disk,
&PartitionStyle
);
if (!NT_SUCCESS (Status)) {
goto done;
}
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
0,
Disk->ScratchBuffer
);
if (!NT_SUCCESS (Status)) {
goto done;
}
Mbr = (PMASTER_BOOT_RECORD) Disk->ScratchBuffer;
//
// Zero out all partition entries, the AA55 signature
// and the NTFT disk signature.
//
RtlZeroMemory (&Mbr->Partition, sizeof (Mbr->Partition));
Mbr->Signature = 0;
Mbr->DiskSignature = 0;
//
// Then write the sector back to the drive.
//
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
0,
Mbr
);
//
// If this was a GPT disk, we null out the primary and backup partition
// table header.
//
if (PartitionStyle == PARTITION_STYLE_GPT) {
RtlZeroMemory (Disk->ScratchBuffer, Disk->SectorSize);
//
// Erase the primary partition table header.
//
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
1,
Disk->ScratchBuffer
);
if (!NT_SUCCESS (Status)) {
goto done;
}
//
// Erase the backup partition table header.
//
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
Disk->SectorCount - 1,
Disk->ScratchBuffer
);
}
done:
if (Disk) {
FstubFreeDiskInformation (Disk);
Disk = NULL;
}
return Status;
}
VOID
FstubCopyEntryEFI(
OUT PEFI_PARTITION_ENTRY Entry,
IN PPARTITION_INFORMATION_EX Partition,
IN ULONG SectorSize
)
{
ULONG64 StartingLBA;
ULONG64 EndingLBA;
PAGED_CODE ();
ASSERT ( Entry != NULL );
ASSERT ( Partition != NULL );
ASSERT ( SectorSize != 0 );
//
// Translate and copy the Starting and Ending LBA.
//
StartingLBA = Partition->StartingOffset.QuadPart / SectorSize;
EndingLBA = Partition->StartingOffset.QuadPart + Partition->PartitionLength.QuadPart - 1;
EndingLBA /= (ULONG64) SectorSize;
Entry->StartingLBA = StartingLBA;
Entry->EndingLBA = EndingLBA;
//
// Copy the Type and Id GUIDs. Copy the attributes.
//
Entry->PartitionType = Partition->Gpt.PartitionType;
Entry->UniquePartition = Partition->Gpt.PartitionId;
Entry->Attributes = Partition->Gpt.Attributes;
//
// Copy the partition name.
//
RtlCopyMemory (
Entry->Name,
Partition->Gpt.Name,
sizeof (Entry->Name)
);
}
NTSTATUS
FstubWritePartitionTableEFI(
IN PDISK_INFORMATION Disk,
IN GUID DiskGUID,
IN ULONG32 MaxPartitionCount,
IN ULONG64 FirstUsableLBA,
IN ULONG64 LastUsableLBA,
IN ULONG PartitionTable,
IN ULONG PartitionCount,
IN PPARTITION_INFORMATION_EX PartitionArray
)
/*++
Routine Description:
Write an EFI partition table to the disk.
Arguments:
Disk - The disk we want to write the partition table to.
MaxPartitionCount -
FirstUsableLBA -
LastUsableLBA -
PartitionTable - Which partition table to write to, either the primary
partition table or the backup partition table.
PartitionCount - The count of partitions in the partiton array.
Partitions entries 0 through PartitionCount - 1 will be
initialized from the array. Partition entries PartitionCount
through MaxPartitionCount will be initialized to null.
PartitionArray - The array of partition entries to be written to disk.
The value can be NULL only if PartitionCount is 0. In that case
we will write an empty partition array.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
ULONG i;
ULONG EntrySlot;
ULONG TableSectorCount;
ULONG SectorSize;
ULONG32 CheckSum;
EFI_PARTITION_ENTRY Entry;
PAGED_CODE ();
ASSERT ( Disk != NULL );
SectorSize = Disk->SectorSize;
ASSERT ( MaxPartitionCount >= 128 );
ASSERT ( PartitionCount <= MaxPartitionCount );
//
// TableSectorCount is the number of blocks that the partition table
// occupies.
//
TableSectorCount =
( PARTITION_ENTRY_SIZE * MaxPartitionCount + SectorSize - 1 ) / SectorSize;
//
// Write the partition table entry array before writing the partition
// table header so we can calculate the checksum along the way.
//
CheckSum = 0;
EntrySlot = 0;
//
// First, copy all non-NULL entries.
//
for (i = 0; i < PartitionCount ; i++) {
//
// Do not write NULL entries to disk. Note that this does not
// prevent other tools from writing valid, NULL entries to the
// drive. It just prevents us from doing it.
//
if ( IS_NULL_GUID ( PartitionArray [ i ].Gpt.PartitionType) ) {
continue;
}
FstubCopyEntryEFI ( &Entry, &PartitionArray [i], SectorSize );
Status = FstubWriteEntryEFI (
Disk,
TableSectorCount,
EntrySlot,
&Entry,
PartitionTable,
FALSE,
&CheckSum
);
if ( !NT_SUCCESS (Status) ) {
return Status;
}
EntrySlot++;
}
//
// Next, copy all NULL entries at the end.
//
for (i = EntrySlot; i < MaxPartitionCount; i++) {
RtlZeroMemory (&Entry, sizeof (Entry));
Status = FstubWriteEntryEFI (
Disk,
TableSectorCount,
i,
&Entry,
PartitionTable,
FALSE,
&CheckSum
);
if ( !NT_SUCCESS (Status) ) {
return Status;
}
}
//
// Write the partition table header to disk.
//
Status = FstubWriteHeaderEFI (
Disk,
TableSectorCount,
DiskGUID,
MaxPartitionCount,
FirstUsableLBA,
LastUsableLBA,
CheckSum,
PartitionTable
);
return Status;
}
NTSTATUS
FstubReadHeaderEFI(
IN PDISK_INFORMATION Disk,
IN ULONG PartitionTable,
OUT PEFI_PARTITION_HEADER* HeaderBuffer
)
/*++
Routine Description:
Read in and validate the EFI partition table header.
The algorithm for validating the partition table header is as follows:
1) Check the Partitin Table Signature, Revision and Size.
2) Check the Partition Table CRC.
3) Check that the MyLBA entry to the LBA that contains the Partition
Table.
4) Check that the CRC of the partition Entry Array is correct.
Arguments:
Disk - The disk to read the EFI partition table header from.
PartitionTable - Whether to read the primary or backup partition table.
HeaderBuffer - Pointer to a buffer when the header table pointer will be
copied on success. Note that, the header table is physically
stored in the disk's scratch buffer.
Return Values:
STATUS_SUCCESS - If the header was successfully read.
STATUS_DISK_CORRUPT_ERROR - If the specified header is invalid and/or
corrupt.
NTSTATUS code - For other errors.
--*/
{
NTSTATUS Status;
ULONG64 MyLBA;
ULONG64 AlternateLBA;
ULONG32 CheckSum;
ULONG32 Temp;
ULONG FullSectorCount;
ULONG MaxPartitionCount;
PVOID Buffer;
ULONG i;
ULONG PartialSectorEntries;
PEFI_PARTITION_HEADER Header;
PAGED_CODE ();
ASSERT ( Disk != NULL );
ASSERT ( IS_VALID_DISK_INFO ( Disk ) );
ASSERT ( HeaderBuffer != NULL );
//
// Initialization
//
Buffer = NULL;
*HeaderBuffer = NULL;
if ( PartitionTable == PRIMARY_PARTITION_TABLE) {
MyLBA = 1;
AlternateLBA = Disk->SectorCount - 1;
} else {
MyLBA = Disk->SectorCount - 1;
AlternateLBA = 1;
}
//
// Read in the primary partition table header.
//
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
MyLBA,
Disk->ScratchBuffer
);
if ( !NT_SUCCESS ( Status ) ) {
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: Could not read sector %I64x\n",
MyLBA));
goto done;
}
Header = (PEFI_PARTITION_HEADER) Disk->ScratchBuffer;
//
// Check Signature, Revision and size.
//
if ( Header->Signature != EFI_PARTITION_TABLE_SIGNATURE ||
Header->Revision != EFI_PARTITION_TABLE_REVISION ||
Header->HeaderSize != sizeof (EFI_PARTITION_HEADER) ) {
Status = STATUS_DISK_CORRUPT_ERROR;
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: Partition Header Invalid\n"
" Header Signature / Revision / Size mismatch\n"));
goto done;
}
//
// Check the partition table CRC. The assumption here is that the
// CRC is computed with a value of 0 for the HeaderCRC field.
//
Temp = Header->HeaderCRC32;
Header->HeaderCRC32 = 0;
CheckSum = RtlComputeCrc32 ( 0, Header, Header->HeaderSize );
Header->HeaderCRC32 = Temp;
if (CheckSum != Header->HeaderCRC32) {
Status = STATUS_DISK_CORRUPT_ERROR;
goto done;
}
//
// Validate the MyLBA.
//
//
// NB: We CANNOT validate AlternateLBA here. If we do, then when a disk
// is grown or shrunk we will fail.
//
if ( Header->MyLBA != MyLBA ) {
Status = STATUS_DISK_CORRUPT_ERROR;
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: Partition Header Invalid\n"
" MyLBA or AlternateLBA is incorrect\n"));
goto done;
}
//
// Read and CRC the Partition Entry Array.
//
//
// First we read and checksum all full sectors.
//
FullSectorCount = Header->NumberOfEntries * PARTITION_ENTRY_SIZE;
FullSectorCount /= Disk->SectorSize;
Buffer = ExAllocatePoolWithTag (
NonPagedPool,
Disk->SectorSize,
FSTUB_TAG
);
if ( Buffer == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
CheckSum = 0;
for (i = 0; i < FullSectorCount; i++) {
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
Header->PartitionEntryLBA + i,
Buffer
);
if (!NT_SUCCESS (Status)) {
goto done;
}
CheckSum = RtlComputeCrc32 (
CheckSum,
Buffer,
Disk->SectorSize
);
}
//
// Next we read and checksum the final, partial sector. Note that this
// is not very likely to ever get executed. The way we write the partition
// table, it will never contain partial sectors as a part of the partition
// array.
//
PartialSectorEntries = Header->NumberOfEntries * PARTITION_ENTRY_SIZE;
PartialSectorEntries %= FullSectorCount;
if ( PartialSectorEntries ) {
//
// Read the remaining sector which contains some partition entries.
//
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
Header->PartitionEntryLBA + FullSectorCount,
Buffer
);
if (!NT_SUCCESS (Status)) {
goto done;
}
for (i = 0; i < PartialSectorEntries; i++) {
CheckSum = RtlComputeCrc32 (
CheckSum,
&(((PEFI_PARTITION_ENTRY)Buffer)[ i ]),
Disk->SectorSize
);
}
}
if ( Header->PartitionEntryCRC32 != CheckSum ) {
Status = STATUS_DISK_CORRUPT_ERROR;
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"FSTUB: Partition Table Invalid\n"
" Partition Array CRC invalid\n"));
goto done;
}
*HeaderBuffer = Header;
Status = STATUS_SUCCESS;
done:
if ( Buffer != NULL ) {
ExFreePool ( Buffer );
Buffer = NULL;
}
if (!NT_SUCCESS (Status)) {
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_ERROR_LEVEL,
"FSTUB: %s EFI Partition table is bad.\n",
PartitionTable == PRIMARY_PARTITION_TABLE ?
"Primary" : "Backup"));
}
return Status;
}
NTSTATUS
FstubReadPartitionTableEFI(
IN PDISK_INFORMATION Disk,
IN ULONG PartitionTable,
OUT PDRIVE_LAYOUT_INFORMATION_EX* ReturnedDriveLayout
)
/*++
Routine Description:
This routine is called to read the partition table on an EFI-partitioned
disk.
Arguments:
Disk - The disk we should read the partition table from.
PartitionTable - Which partition table to read, the primary or backup
table.
ReturnedDriveLayout - Pointer to pointer to the buffer where
the partition information will be stored.
Return Values:
STATUS_SUCCESS - If the partition table information was succesfully read
into ReturnedDriveLayoutInformation.
Otherwise - Failure.
Notes:
The memory allocated by this routine must be free by the caller using
ExFreePool().
--*/
{
NTSTATUS Status;
ULONG i;
ULONG j;
ULONG PartitionCount;
ULONG CurrentSector;
ULONG SectorNumber;
ULONG SectorIndex;
PVOID Block;
ULONG MaxPartitionCount;
ULONG DriveLayoutSize;
ULONG PartitionsPerBlock;
PEFI_PARTITION_ENTRY EntryArray;
PEFI_PARTITION_ENTRY Entry;
PEFI_PARTITION_HEADER Header;
PDRIVE_LAYOUT_INFORMATION_EX DriveLayout;
PPARTITION_INFORMATION_EX PartitionInfo;
ULONG64 PartitionEntryLBA;
PAGED_CODE ();
ASSERT ( Disk != NULL );
//
// Initialization
//
DriveLayout = NULL;
//
// Read the partition table header.
//
Status = FstubReadHeaderEFI ( Disk, PartitionTable, &Header );
if (!NT_SUCCESS (Status)) {
goto done;
}
//
// Allocate space the maximum number of EFI partitions on this drive.
//
MaxPartitionCount = Header->NumberOfEntries;
DriveLayoutSize = FIELD_OFFSET (DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[0]) +
MaxPartitionCount * sizeof (PARTITION_INFORMATION_EX);
DriveLayout = ExAllocatePoolWithTag ( NonPagedPool,
DriveLayoutSize,
FSTUB_TAG
);
if ( DriveLayout == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
//
// Copy the EFI disk layout information.
//
DriveLayout->PartitionStyle = PARTITION_STYLE_GPT;
DriveLayout->Gpt.StartingUsableOffset.QuadPart =
Header->FirstUsableLBA * Disk->SectorSize;
DriveLayout->Gpt.UsableLength.QuadPart =
(Header->LastUsableLBA - Header->FirstUsableLBA) * Disk->SectorSize;
DriveLayout->Gpt.MaxPartitionCount = MaxPartitionCount;
RtlCopyMemory (
&DriveLayout->Gpt.DiskId,
&Header->DiskGUID,
sizeof (GUID)
);
//
// Read in each block that contains entries in the partition table
// array, then iterate through the partition table array and map
// each EFI_PARTITION_ENTRY structure into an PARTITION_INFORMATION_GPT
// structure.
//
PartitionEntryLBA = Header->PartitionEntryLBA;
Header = NULL;
EntryArray = (PEFI_PARTITION_ENTRY) Disk->ScratchBuffer;
PartitionCount = 0;
CurrentSector = -1;
PartitionsPerBlock = (ULONG) (Disk->SectorSize / PARTITION_ENTRY_SIZE);
for (i = 0; i < MaxPartitionCount; i++) {
SectorNumber = i / PartitionsPerBlock ;
SectorIndex = i % PartitionsPerBlock ;
//
// If we have a sector other than the current sector read in,
// read in the current sector at this time.
//
if ( SectorNumber != CurrentSector ) {
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
PartitionEntryLBA + SectorNumber,
EntryArray
);
if ( !NT_SUCCESS (Status) ) {
goto done;
}
CurrentSector = SectorNumber;
}
Entry = &EntryArray[ SectorIndex ];
//
// We ignore NULL entries in the partition table. NOTE: Is this
// dangerous?
//
if ( IS_NULL_GUID (Entry->PartitionType ) ) {
continue;
}
//
// Copy the data into the EFI partition array.
//
PartitionInfo = &DriveLayout->PartitionEntry[PartitionCount];
PartitionInfo->StartingOffset.QuadPart = Entry->StartingLBA;
PartitionInfo->StartingOffset.QuadPart *= (ULONG64) Disk->SectorSize;
PartitionInfo->PartitionLength.QuadPart =
(Entry->EndingLBA - Entry->StartingLBA) + 1;
PartitionInfo->PartitionLength.QuadPart *= (ULONG64) Disk->SectorSize;
PartitionInfo->PartitionStyle = PARTITION_STYLE_GPT;
PartitionInfo->Gpt.PartitionType = Entry->PartitionType;
PartitionInfo->Gpt.PartitionId = Entry->UniquePartition;
PartitionInfo->Gpt.Attributes = Entry->Attributes;
RtlCopyMemory (PartitionInfo->Gpt.Name,
Entry->Name,
sizeof (PartitionInfo->Gpt.Name)
);
PartitionInfo->RewritePartition = FALSE;
//
// The PartitionNumber field of PARTITION_INFORMATION_EX is
// not initialized by us. Instead, it is initialized in the
// calling driver
//
PartitionInfo->PartitionNumber = -1;
PartitionCount++;
}
//
// Fill in the remaining fields of the DRIVE_LAYOUT structure.
//
DriveLayout->PartitionCount = PartitionCount;
done:
//
// Free all resources
//
if (!NT_SUCCESS (Status)) {
//
// DriveLayout is not being returned, so deallocate it if it has
// be allocated.
//
if ( DriveLayout ) {
ExFreePool (DriveLayout);
DriveLayout = NULL;
}
*ReturnedDriveLayout = NULL;
} else {
*ReturnedDriveLayout = DriveLayout;
DriveLayout = NULL;
}
return Status;
}
NTSTATUS
FstubVerifyPartitionTableEFI(
IN PDISK_INFORMATION Disk,
IN BOOLEAN FixErrors
)
/*++
Routine Description:
Verify that a partition table is correct.
Arguments:
Disk - The disk whose partition table(s) should be verified.
FixErrors - If, TRUE, this routine attempts to fix any errors in the
partition table. Otherwise, this routine only checkes whether
there are errrors in the partition table, "read only".
Return Value:
STATUS_SUCCESS - If the final partition table, after any changes (only
when FixErrors is TRUE), is valid.
STATUS_DISK_CORRUPT - If the final partition table, after any changes (only
when FixErrors is TRUE) is not valid.
Other NTSTATUS code - Other type of failure.
--*/
{
NTSTATUS Status;
ULONG64 i;
ULONG64 SourceStartingLBA;
ULONG64 DestStartingLBA;
ULONG SectorCount;
BOOLEAN PrimaryValid;
BOOLEAN BackupValid;
ULONG GoodTable;
ULONG BadTable;
PEFI_PARTITION_HEADER Header;
PEFI_PARTITION_HEADER GoodHeader;
PAGED_CODE ();
//
// Initialization
//
Header = NULL;
GoodHeader = NULL;
PrimaryValid = FALSE;
BackupValid = FALSE;
GoodHeader = ExAllocatePoolWithTag (
NonPagedPool,
sizeof (EFI_PARTITION_HEADER),
FSTUB_TAG
);
if ( GoodHeader == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = FstubReadHeaderEFI (
Disk,
PRIMARY_PARTITION_TABLE,
&Header
);
if ( NT_SUCCESS (Status) ) {
PrimaryValid = TRUE;
ASSERT (Header != NULL);
RtlCopyMemory (GoodHeader,
Header,
sizeof (EFI_PARTITION_HEADER)
);
}
Status = FstubReadHeaderEFI (
Disk,
BACKUP_PARTITION_TABLE,
&Header
);
if ( NT_SUCCESS (Status) ) {
BackupValid = TRUE;
ASSERT (Header != NULL);
RtlCopyMemory (GoodHeader,
Header,
sizeof (EFI_PARTITION_HEADER)
);
}
//
// If both primary and backup partition tables are valid, return success.
//
if ( PrimaryValid && BackupValid ) {
Status = STATUS_SUCCESS;
goto done;
}
//
// If both primary and backup partition tables are bad, return failure.
if ( !PrimaryValid && !BackupValid ) {
Status = STATUS_DISK_CORRUPT_ERROR;
goto done;
}
//
// If one of the partition tables is bad, and we were not instructed to
// fix it, return failure.
//
if ( !FixErrors ) {
Status = STATUS_DISK_CORRUPT_ERROR;
goto done;
}
//
// If we've reached this point, one or the other of the tables is
// bad and we've been instructed to fix it.
//
ASSERT ( GoodHeader != NULL );
//
// SectorCount is the number of sectors occupied by the partition table.
//
SectorCount = ( PARTITION_ENTRY_SIZE * Header->NumberOfEntries + Disk->SectorSize - 1 ) / Disk->SectorSize;
if ( PrimaryValid ) {
GoodTable = PRIMARY_PARTITION_TABLE;
BadTable = BACKUP_PARTITION_TABLE;
SourceStartingLBA = 2;
DestStartingLBA = Disk->SectorCount - SectorCount - 1;
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_ERROR_LEVEL,
"FSTUB: Restoring backup partition table from primary\n"));
} else {
ASSERT ( BackupValid );
GoodTable = BACKUP_PARTITION_TABLE;
BadTable = PRIMARY_PARTITION_TABLE;
SourceStartingLBA = Disk->SectorCount - SectorCount - 1;
DestStartingLBA = 2;
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_ERROR_LEVEL,
"FSTUB: Restoring primary partition table from backup\n"));
}
//
// First copy the good partition table array over the bad partition table
// array. This does not need to be checksummed since the checksum in
// the good header will still be valid for this one.
//
for (i = 0; i < SectorCount; i++) {
Status = FstubReadSector (
Disk->DeviceObject,
Disk->SectorSize,
SourceStartingLBA + i,
Disk->ScratchBuffer
);
if ( !NT_SUCCESS (Status) ) {
goto done;
}
Status = FstubWriteSector (
Disk->DeviceObject,
Disk->SectorSize,
DestStartingLBA + i,
Disk->ScratchBuffer
);
if ( !NT_SUCCESS (Status) ) {
goto done;
}
}
//
// Next, write out the header.
//
Status = FstubWriteHeaderEFI (
Disk,
SectorCount,
GoodHeader->DiskGUID,
GoodHeader->NumberOfEntries,
GoodHeader->FirstUsableLBA,
GoodHeader->LastUsableLBA,
GoodHeader->PartitionEntryCRC32,
BadTable
);
done:
if ( GoodHeader ) {
ExFreePool ( GoodHeader );
GoodHeader = NULL;
}
return Status;
}
NTSTATUS
FstubUpdateDiskGeometryEFI(
IN PDISK_INFORMATION OldDisk,
IN PDISK_INFORMATION NewDisk
)
/*++
Routine Description:
When a disk is grown or shrunk this API needs to be called to properly
update the EFI partition tables. In particular, the backup partition table
needs to be moved to be at the end of the disk.
Algorithm:
We read in the old partition table, updat the size of the disk, then
write out the new partition table given the changed disk size.
Arguments:
OldDisk - A disk information object representing the old geometry.
NewDisk - A disk information object representing the new goemetry.
Return Values:
NTSTATUS Code.
--*/
{
NTSTATUS Status;
ULONG64 i;
ULONG64 SourceStartingLBA;
ULONG64 DestStartingLBA;
ULONG SectorCount;
PEFI_PARTITION_HEADER Header;
PAGED_CODE ();
//
// Initialization
//
Header = NULL;
Status = FstubReadHeaderEFI (
OldDisk,
BACKUP_PARTITION_TABLE,
&Header
);
if ( !NT_SUCCESS (Status) ) {
return Status;
}
//
// SectorCount is the number of sectors occupied by the partition table.
//
SectorCount = ( PARTITION_ENTRY_SIZE * Header->NumberOfEntries + OldDisk->SectorSize - 1 ) / OldDisk->SectorSize;
//
// Write the partition table header for the primary partition table. Note
// that the primary partition table does not need to be moved since it
// is at the beginning of the disk.
//
Status = FstubWriteHeaderEFI (
NewDisk,
SectorCount,
Header->DiskGUID,
Header->NumberOfEntries,
Header->FirstUsableLBA,
Header->LastUsableLBA,
Header->PartitionEntryCRC32,
PRIMARY_PARTITION_TABLE
);
//
// Write the partition table header for the backup table.
//
Status = FstubWriteHeaderEFI (
NewDisk,
SectorCount,
Header->DiskGUID,
Header->NumberOfEntries,
Header->FirstUsableLBA,
Header->LastUsableLBA,
Header->PartitionEntryCRC32,
BACKUP_PARTITION_TABLE
);
if ( !NT_SUCCESS (Status) ) {
return Status;
}
//
// Calculate the location of the backup table.
//
SourceStartingLBA = OldDisk->SectorCount - SectorCount - 1;
DestStartingLBA = NewDisk->SectorCount - SectorCount - 1;
//
// And write the backup table.
//
for (i = 0; i < SectorCount; i++) {
Status = FstubReadSector (
OldDisk->DeviceObject,
OldDisk->SectorSize,
SourceStartingLBA + i,
OldDisk->ScratchBuffer
);
if ( !NT_SUCCESS (Status) ) {
return Status;
}
Status = FstubWriteSector (
NewDisk->DeviceObject,
NewDisk->SectorSize,
DestStartingLBA + i,
OldDisk->ScratchBuffer
);
if ( !NT_SUCCESS (Status) ) {
return Status;
}
}
#if DBG
//
// Make a sanity check that we actually did this correctly.
//
Status = FstubVerifyPartitionTableEFI ( NewDisk, FALSE );
ASSERT ( NT_SUCCESS ( Status ) );
#endif
return Status;
}
NTSTATUS
FstubWriteSector(
IN PDEVICE_OBJECT DeviceObject,
IN ULONG SectorSize,
IN ULONG64 SectorNumber,
IN PVOID Buffer
)
/*++
Routine Description:
Read a sector from the device DeviceObject.
Arguments:
DeviceObject - The object representing the device.
SectorSize - The size of one sector on the device.
SectorNumber - The sector number to write.
Buffer - The buffer to write. The buffer must be of size SectorSize.
Return Values:
NTSTATUS code.
--*/
{
NTSTATUS Status;
PIRP Irp;
IO_STATUS_BLOCK IoStatus;
PIO_STACK_LOCATION IrpStack;
KEVENT Event;
LARGE_INTEGER Offset;
PAGED_CODE ();
ASSERT ( DeviceObject );
ASSERT ( Buffer );
ASSERT ( SectorSize != 0 );
Offset.QuadPart = (SectorNumber * SectorSize);
KeInitializeEvent (&Event, NotificationEvent, FALSE);
Irp = IoBuildSynchronousFsdRequest ( IRP_MJ_WRITE,
DeviceObject,
Buffer,
SectorSize,
&Offset,
&Event,
&IoStatus
);
if ( Irp == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
IrpStack = IoGetNextIrpStackLocation (Irp);
IrpStack->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
Status = IoCallDriver( DeviceObject, Irp );
if (Status == STATUS_PENDING) {
Status = KeWaitForSingleObject (
&Event,
Executive,
KernelMode,
FALSE,
NULL
);
Status = IoStatus.Status;
}
return Status;
}
NTSTATUS
FstubReadSector(
IN PDEVICE_OBJECT DeviceObject,
IN ULONG SectorSize,
IN ULONG64 SectorNumber,
OUT PVOID Buffer
)
/*++
Routine Description:
Read a logical block from the device (disk).
Arguments:
DeviceObject - The device that we are going to read from.
SectorSize - The size of the block and the size of the Buffer.
SectorNumber - The Logical Block Number we are going to read.
Buffer - The buffer into which we are going to read the block.
Return Values:
NTSTATUS
--*/
{
NTSTATUS Status;
PIRP Irp;
IO_STATUS_BLOCK IoStatus;
PIO_STACK_LOCATION IrpStack;
KEVENT Event;
LARGE_INTEGER Offset;
PAGED_CODE ();
ASSERT ( DeviceObject );
ASSERT ( Buffer );
ASSERT ( SectorSize != 0 );
Offset.QuadPart = (SectorNumber * SectorSize);
KeInitializeEvent (&Event, NotificationEvent, FALSE);
Irp = IoBuildSynchronousFsdRequest ( IRP_MJ_READ,
DeviceObject,
Buffer,
SectorSize,
&Offset,
&Event,
&IoStatus
);
if ( Irp == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
IrpStack = IoGetNextIrpStackLocation (Irp);
IrpStack->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
Status = IoCallDriver( DeviceObject, Irp );
if (Status == STATUS_PENDING) {
KeWaitForSingleObject ( &Event,
Executive,
KernelMode,
FALSE,
NULL
);
Status = IoStatus.Status;
}
return Status;
}
//
// Debugging functions.
//
#if DBG
PCHAR
FstubDbgGuidToString(
IN GUID* Guid,
PCHAR StringBuffer
)
{
sprintf (StringBuffer,
"{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
Guid->Data1,
Guid->Data2,
Guid->Data3,
Guid->Data4[0],
Guid->Data4[1],
Guid->Data4[2],
Guid->Data4[3],
Guid->Data4[4],
Guid->Data4[5],
Guid->Data4[6],
Guid->Data4[7]
);
return StringBuffer;
}
VOID
FstubDbgPrintSetPartitionEx(
IN PSET_PARTITION_INFORMATION_EX SetPartition,
IN ULONG PartitionNumber
)
/*++
Routine Description:
Print contents of the SET_PARTITION_INFORMATION structure to the
debugger.
Arguments:
SetPartition - A valid SET_PARTITION_INFORMATION_EX structure.
Return Value:
None.
Mode:
Checked build only.
--*/
{
CHAR GuidStringBuffer [40];
PAGED_CODE ();
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"\n"
"FSTUB:\n"
"SET_PARTITION_INFORMATION_EX %p\n"));
if ( SetPartition->PartitionStyle != PARTITION_STYLE_MBR &&
SetPartition->PartitionStyle != PARTITION_STYLE_GPT ) {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"ERROR: PartitionStyle is invalid %d\n",
SetPartition->PartitionStyle));
}
if ( SetPartition->PartitionStyle == PARTITION_STYLE_MBR ) {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"Type: %8.8x\n\n",
SetPartition->Mbr.PartitionType));
} else {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"[%d] %ws\n",
PartitionNumber,
SetPartition->Gpt.Name));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" ATTR %-16I64x\n",
SetPartition->Gpt.Attributes));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" TYPE %s\n",
FstubDbgGuidToString(&SetPartition->Gpt.PartitionType,
GuidStringBuffer)));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" ID %s\n",
FstubDbgGuidToString(&SetPartition->Gpt.PartitionId,
GuidStringBuffer)));
}
KdPrintEx((DPFLTR_FSTUB_ID, FSTUB_VERBOSE_LEVEL, "\n"));
}
VOID
FstubDbgPrintPartition(
IN PPARTITION_INFORMATION Partition,
IN ULONG PartitionCount
)
/*++
Routine Description:
Print a PARTITION_INFORMATION structure to the debugger.
Arguments:
Partition - Pointer to a valid PARTITION_INFORMATION structure.
PartitionCount - The number of partitions in the partition table or
zero if unknown.
Return Value:
None.
--*/
{
ULONG PartitionNumber;
PAGED_CODE ();
//
// Sanity check the data.
//
if ( (Partition->BootIndicator != TRUE &&
Partition->BootIndicator != FALSE) ||
(Partition->RecognizedPartition != TRUE &&
Partition->RecognizedPartition != FALSE) ||
(Partition->RewritePartition != TRUE &&
Partition->RewritePartition != FALSE) ) {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"Invalid partition information at %p\n",
Partition));
}
if (Partition->PartitionNumber > PartitionCount) {
PartitionNumber = -1;
} else {
PartitionNumber = Partition->PartitionNumber;
}
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"[%-2d] %-16I64x %-16I64x %2.2x %c %c %c\n",
PartitionNumber,
Partition->StartingOffset.QuadPart,
Partition->PartitionLength.QuadPart,
Partition->PartitionType,
Partition->BootIndicator ? 'x' : ' ',
Partition->RecognizedPartition ? 'x' : ' ',
Partition->RewritePartition ? 'x' : ' '));
}
VOID
FstubDbgPrintDriveLayout(
IN PDRIVE_LAYOUT_INFORMATION Layout
)
/*++
Routine Description:
Print out a DRIVE_LAYOUT_INFORMATION structure to the debugger.
Arguments:
Layout - Pointer to a valid DRIVE_LAYOUT_INFORMATION structure.
Return Value:
None.
Mode:
Checked build only.
--*/
{
ULONG i;
PAGED_CODE ();
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"\n"
"FSTUB:\n"
"DRIVE_LAYOUT %p\n",
Layout));
//
// Warn if the partition count is not a factor of 4. This is probably a
// bad partition information structure, but we'll continue on anyway.
//
if (Layout->PartitionCount % 4 != 0) {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"WARNING: Partition count should be a factor of 4.\n"));
}
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"PartitionCount: %d\n",
Layout->PartitionCount));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"Signature: %8.8x\n\n",
Layout->Signature));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" ORD Offset Length Type BI RP RW\n"));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" ------------------------------------------------------------\n"));
for (i = 0; i < Layout->PartitionCount; i++) {
FstubDbgPrintPartition (
&Layout->PartitionEntry[i],
Layout->PartitionCount
);
}
KdPrintEx((DPFLTR_FSTUB_ID, FSTUB_VERBOSE_LEVEL, "\n"));
}
VOID
FstubDbgPrintPartitionEx(
IN PPARTITION_INFORMATION_EX PartitionEx,
IN ULONG PartitionCount
)
/*++
Routine Description:
Dump a PARTITION_INFORMATION_EX structure.
Arguments:
PartitionEx - Pointer to a partition to dump.
PartitionCount - The number of partitions. This is used to determine
whether a particular partition ordinal is valid or not.
Return Value:
None.
--*/
{
ULONG Style;
ULONG PartitionNumber;
CHAR GuidStringBuffer [40];
PAGED_CODE ();
Style = PartitionEx->PartitionStyle;
if (Style != PARTITION_STYLE_MBR &&
Style != PARTITION_STYLE_GPT) {
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_ERROR_LEVEL,
"ERROR: PartitionStyle is invalid %d for partition %p\n",
PartitionEx));
return;
}
//
// We use -1 to denote an invalid partition ordinal.
//
if (PartitionEx->PartitionNumber < PartitionCount) {
PartitionNumber = PartitionEx->PartitionNumber;
} else {
PartitionNumber = -1;
}
if (Style == PARTITION_STYLE_MBR) {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" [%-2d] %-16I64x %-16I64x %2.2x %c %c %c\n",
PartitionNumber,
PartitionEx->StartingOffset.QuadPart,
PartitionEx->PartitionLength.QuadPart,
PartitionEx->Mbr.PartitionType,
PartitionEx->Mbr.BootIndicator ? 'x' : ' ',
PartitionEx->Mbr.RecognizedPartition ? 'x' : ' ',
PartitionEx->RewritePartition ? 'x' : ' '));
} else {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"[%-2d] %ws\n",
PartitionNumber,
PartitionEx->Gpt.Name));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" OFF %-16I64x LEN %-16I64x ATTR %-16I64x %s\n",
PartitionEx->StartingOffset.QuadPart,
PartitionEx->PartitionLength.QuadPart,
PartitionEx->Gpt.Attributes,
PartitionEx->RewritePartition ? "R/W" : ""));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" TYPE %s\n",
FstubDbgGuidToString(&PartitionEx->Gpt.PartitionType,
GuidStringBuffer)));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" ID %s\n",
FstubDbgGuidToString(&PartitionEx->Gpt.PartitionId,
GuidStringBuffer)));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"\n"));
}
}
VOID
FstubDbgPrintDriveLayoutEx(
IN PDRIVE_LAYOUT_INFORMATION_EX LayoutEx
)
/*++
Routine Description:
Print the DRIVE_LAYOUT_INFORMATION_EX to the debugger.
Arguments:
LayoutEx - A pointer to a valid DRIVE_LAYOUT_INFORMATION_EX structure.
Return Value:
None.
Mode:
Debugging function. Checked build only.
--*/
{
ULONG i;
ULONG Style;
CHAR GuidStringBuffer[40];
PAGED_CODE ();
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"\n"
"FSTUB:\n"
"DRIVE_LAYOUT_EX %p\n",
LayoutEx));
Style = LayoutEx->PartitionStyle;
if (Style != PARTITION_STYLE_MBR && Style != PARTITION_STYLE_GPT) {
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_ERROR_LEVEL,
"ERROR: invalid partition style %d for layout %p\n",
Style,
LayoutEx));
return;
}
if (Style == PARTITION_STYLE_MBR &&
LayoutEx->PartitionCount % 4 != 0) {
KdPrintEx((DPFLTR_FSTUB_ID,
DPFLTR_WARNING_LEVEL,
"WARNING: Partition count is not a factor of 4, (%d)\n",
LayoutEx->PartitionCount));
}
if (Style == PARTITION_STYLE_MBR) {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"Signature: %8.8x\n",
LayoutEx->Mbr.Signature));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"PartitionCount %d\n\n",
LayoutEx->PartitionCount));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
" ORD Offset Length Type BI RP RW\n"));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"------------------------------------------------------------\n"));
} else {
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"DiskId: %s\n",
FstubDbgGuidToString(&LayoutEx->Gpt.DiskId,
GuidStringBuffer)));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"StartingUsableOffset: %I64x\n",
LayoutEx->Gpt.StartingUsableOffset.QuadPart));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"UsableLength: %I64x\n",
LayoutEx->Gpt.UsableLength));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"MaxPartitionCount: %d\n",
LayoutEx->Gpt.MaxPartitionCount));
KdPrintEx((DPFLTR_FSTUB_ID,
FSTUB_VERBOSE_LEVEL,
"PartitionCount %d\n\n",
LayoutEx->PartitionCount));
}
for (i = 0; i < LayoutEx->PartitionCount; i++) {
FstubDbgPrintPartitionEx (
&LayoutEx->PartitionEntry[i],
LayoutEx->PartitionCount
);
}
}
#endif // DBG