/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    FTMan

File Name:

	DiskMap.cpp

Abstract:

    Implementation of classes used to keep the disk array map in memory. Used in retrieving partitions and free spaces, 
	in creating and deleting partitions

Author:

    Cristian Teodorescu      October 23, 1998

Notes:

Revision History:

--*/

#include "stdafx.h"

#include "DiskMap.h"
#include "FrSpace.h"
#include "PhPart.h"
#include "Resource.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//#define CSEC_FAT		32680
//#define CSEC_FAT32MEG	65536

///////////////////////////////////////////////////////////////////////////////////////////////////
// CDiskMap

////////////////////////////////////////////////////////////////////////////////////////////////////
// Public methods

/*
Public method:		LoadDiskInfo

Purpose:			Load the disk layout and geometry into this object
					
Parameters:			[OUT] CString& strErrors
						All errors found during LoadDiskInfo are reported through this string
					[OUT] BOOL& bMissingDisk
						Reported TRUE if the disk m_dwDiskNumber is not found ( CreateFile returns invalid handle )
						This may notify the calling procedure that the search for installed disks is over

Return value:		TRUE if the disk information is loaded successfully
*/

BOOL CDiskMap::LoadDiskInfo( 
						CString& strErrors, 
						BOOL& bMissingDisk )
{
	MY_TRY

	ASSERT( m_dwDiskNumber >= 0 );

	m_bLoaded = FALSE;
	strErrors = _T("");
	bMissingDisk = FALSE;
	

	CString strFileName;
	strFileName.Format(_T("\\\\.\\PHYSICALDRIVE%lu"), m_dwDiskNumber); 
		
	// Try to open the disk
	HANDLE hDisk = CreateFile( 
						strFileName, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
						NULL, OPEN_EXISTING, 0 , INVALID_HANDLE_VALUE );
	if( hDisk == INVALID_HANDLE_VALUE )
	{
		// This disk is not installed in the system
		bMissingDisk = TRUE;
		return FALSE;
	}
			
	// Read the geometry of the disk
	ULONG			ulBytes;
	DISK_GEOMETRY	geometry;
	if( !DeviceIoControl(hDisk, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,
								&geometry, sizeof(geometry), &ulBytes, NULL ) )
	{	
		AddError( strErrors, IDS_ERR_GET_DRIVE_GEOMETRY, TRUE );
		CloseHandle( hDisk );
		return FALSE;
	}

	m_llSectorSize = geometry.BytesPerSector;
	m_llTrackSize = geometry.SectorsPerTrack * m_llSectorSize;
	m_llCylinderSize = geometry.TracksPerCylinder * m_llTrackSize;
	m_llDiskSize = geometry.Cylinders.QuadPart * m_llCylinderSize;

	// Allocate some space for the drive layout buffer
	if( m_pBuffer == NULL )
	{
		ASSERT( m_dwBufferSize == 0 );
		
		m_dwBufferSize = sizeof( DRIVE_LAYOUT_INFORMATION ) + 20*sizeof(PARTITION_INFORMATION);
		// Allocate space for the drive layout information
		m_pBuffer = (PDRIVE_LAYOUT_INFORMATION)LocalAlloc(0, m_dwBufferSize);
		if( !m_pBuffer )
		{
			m_dwBufferSize = 0;
			CloseHandle( hDisk );
			AddError( strErrors, IDS_ERR_ALLOCATION, FALSE );
			// There is nothing else to do so return
			return FALSE;
		}
	}
	
	// Read the structure of the disk
	BOOL bResult = DeviceIoControl( 
							hDisk, IOCTL_DISK_GET_DRIVE_LAYOUT, NULL, 0, 
							m_pBuffer, m_dwBufferSize, &ulBytes, NULL );

	// If the buffer is not large enough reallocate it
	while (!bResult && GetLastError() == ERROR_MORE_DATA) 
	{        
		m_dwBufferSize = ulBytes;
		LocalFree( m_pBuffer );
        m_pBuffer = (PDRIVE_LAYOUT_INFORMATION)(LocalAlloc(0, m_dwBufferSize ));
        if (!m_pBuffer) 
		{            
			m_dwBufferSize = 0;
			CloseHandle( hDisk );
			AddError( strErrors, IDS_ERR_ALLOCATION, FALSE );
			return FALSE;
        }        
		BOOL bResult = DeviceIoControl( 
							hDisk, IOCTL_DISK_GET_DRIVE_LAYOUT, NULL, 0, 
							m_pBuffer, m_dwBufferSize, &ulBytes, NULL );
	}
	
	// If we still cannot get the drive layout then return false
	if( !bResult )
	{
		AddError( strErrors, IDS_ERR_GET_DRIVE_LAYOUT, TRUE );
		CloseHandle(hDisk);
		return FALSE;
	}

	m_bLoaded = TRUE;

	return TRUE;
	
	MY_CATCH_AND_THROW
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Public methods

/*
Public method:		SaveDiskInfo

Purpose:			Save the disk layout on disk
					
Parameters:			[OUT] CString& strErrors
						All errors found during SaveDiskInfo are reported through this string
					[OUT] BOOL& bMissingDisk
						Reported TRUE if the disk m_dwDiskNumber is not found ( CreateFile returns invalid handle )
						This may notify the calling procedure that the search for installed disks is over

Return value:		TRUE if the disk information is saved successfully
*/

BOOL CDiskMap::SaveDiskInfo( 
						CString& strErrors, 
						BOOL& bMissingDisk )
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	strErrors = _T("");
	bMissingDisk = FALSE;
	

	CString strFileName;
	strFileName.Format(_T("\\\\.\\PHYSICALDRIVE%lu"), m_dwDiskNumber);
	
	// Try to open the disk
	HANDLE hDisk = CreateFile( 
						strFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
						NULL, OPEN_EXISTING, 0 , INVALID_HANDLE_VALUE );
	if( hDisk == INVALID_HANDLE_VALUE )
	{
		// This disk is not installed in the system
		bMissingDisk = TRUE;
		return FALSE;
	}
			
	ULONG ulBytes;

	BOOL bResult = DeviceIoControl( 
							hDisk, IOCTL_DISK_SET_DRIVE_LAYOUT, m_pBuffer, 
							sizeof(DRIVE_LAYOUT_INFORMATION) + m_pBuffer->PartitionCount*sizeof(PARTITION_INFORMATION),
							NULL, 0, &ulBytes, NULL );

	if( !bResult )
	{
		AddError( strErrors, IDS_ERR_SET_DRIVE_LAYOUT, TRUE );
		CloseHandle( hDisk );
		return FALSE;
	}

	CloseHandle( hDisk );
	m_bLoaded = TRUE;

	return TRUE;

	MY_CATCH_AND_THROW
}


/*
Public method:		ReadPartitions

Purpose:			Retrieve all non-container partitions on the disk and create CPhysicalPartitionData instances for 
					them
					If the disk info is not loaded the method calls LoadDiskInfo first
					
Parameters:			[OUT] CObArray& arrPartitions
						Array of CPhysicalPartitionData containing all physical partitions found on the disk
					[OUT] CString& strErrors
						All errors found during ReadPhysicalPartitions are reported through this string
					[OUT] BOOL& bMissingDisk
						Reported TRUE if the disk m_dwDiskNumber is not found ( CreateFile returns invalid handle )
						This may notify the calling procedure that the search for installed disks is over
					[IN] CItemData* pParentData
						The items' parent data. It will fill the m_pParentData member of all new
						CPhysicalPartititionData instances

Return value:		TRUE if the the method ends successfully
*/

BOOL CDiskMap::ReadPartitions( 
						CObArray& arrPartitions, 
						CString& strErrors, 
						BOOL& bMissingDisk,
						CItemData* pParentData /* = NULL */)
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	arrPartitions.RemoveAll();
	strErrors = _T("");

	if( m_bLoaded )
		bMissingDisk = FALSE;
	else if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		return FALSE;

	// Read all recognized partitions on the disk
	for( DWORD dwIndex = 0; dwIndex < m_pBuffer->PartitionCount; dwIndex++ )
	{
		// A partition that is not logical volume has the highest bit of field
		// PartitionType 0
		if( m_pBuffer->PartitionEntry[dwIndex].RecognizedPartition &&
			!( m_pBuffer->PartitionEntry[dwIndex].PartitionType & 0x80 ) )
		{
			// Create the logical volume item data
			CPhysicalPartitionData* pData = new CPhysicalPartitionData(
														m_dwDiskNumber, 
														m_pBuffer->Signature, 
														&(m_pBuffer->PartitionEntry[dwIndex]),
														( dwIndex < 4 ) ? PT_Primary : PT_InExtendedPartition,
														pParentData,
														TRUE);
			CString strMemberErrors;
			pData->ReadItemInfo( strMemberErrors );
			strErrors += strMemberErrors;
			// Insert the structure in the members' data array This array must be sorted by starting offset
			for( int i = 0; i < arrPartitions.GetSize(); i++ )
			{
				if( pData->m_PartInfo.StartingOffset.QuadPart < ((CPhysicalPartitionData*)arrPartitions[i])->m_PartInfo.StartingOffset.QuadPart )
					break;
			}
			arrPartitions.InsertAt( i, pData );
		}
	}

	return TRUE;

	MY_CATCH_AND_THROW
}

/*
Public method:		ReadFreeSpaces

Purpose:			Retrieve all free spaces on the disk and create CFreeSpaceData instances for 
					them. A free space is a space not contained by a physical partition
					If the disk info is not loaded the method calls LoadDiskInfo first
					
Parameters:			[OUT] CObArray& arrFreeSpaces
						Array of CFreeSpaceData containing all free spaces found on the disk
					[OUT] CString& strErrors
						All errors found during ReadFreeSpaces are reported through this string
					[OUT] BOOL& bMissingDisk
						Reported TRUE if the disk m_dwDiskNumber is not found ( CreateFile returns invalid handle )
						This may notify the calling procedure that the search for installed disks is over
					[IN] CItemData* pParentData
						The items' parent data. It will fill the m_pParentData member of all new
						CFreeSpaceData instances

Return value:		TRUE if the method ends successfully
*/

BOOL CDiskMap::ReadFreeSpaces( 
						CObArray& arrFreeSpaces, 
						CString& strErrors, 
						BOOL& bMissingDisk,
						CItemData* pParentData /* = NULL */)
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	arrFreeSpaces.RemoveAll();
	strErrors = _T("");

	if( m_bLoaded )
		bMissingDisk = FALSE;
	else if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		return FALSE;

	DWORD		dwNextIndex;

	GetExtendedPartitionFreeSpaces( 0, m_llDiskSize, 0, dwNextIndex, arrFreeSpaces, pParentData );	

	for( int i = 0; i < arrFreeSpaces.GetSize(); i++ )
		((CFreeSpaceData*)(arrFreeSpaces[i]))->m_dwFreeSpaceNumber = i+1;
	
	return TRUE;

	MY_CATCH_AND_THROW
}

/*
Public method:		ReadPartitionInformation

Purpose:			Retrieves a partition information from the disk (m_dwDiskNumber) layout

Parameters:			[IN] LONGLONG llPartStartOffset
						Offset of the partition
					[OUT] PARTITION_INFORMATION& partInfo
						Structure to receive the partition info
					[OUT] PARTITION_TYPE& wPartitionType
						Partition type ( primary or partition in ectended partition )
					[OUT] CString& strErrors
						All errors found during ReadPartitionInformation are reported through this string
					[OUT] BOOL& bMissingDisk
						Reported TRUE if the disk m_dwDiskNumber is not found ( CreateFile returns invalid handle )
						This may notify the calling procedure that the search for installed disks is over

Return value:		TRUE	if the query succeeded							
*/

BOOL CDiskMap::ReadPartitionInformation(	
						LONGLONG llPartStartOffset, 
						PARTITION_INFORMATION& partInfo, 
						PARTITION_TYPE& wPartitionType,
						CString& strErrors, 
						BOOL& bMissingDisk )
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	strErrors = _T("");

	if( m_bLoaded )
		bMissingDisk = FALSE;
	else if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		return FALSE;

	// Check all recognized partitions on the disk
	for( DWORD dwIndex = 0; dwIndex < m_pBuffer->PartitionCount; dwIndex++ )
	{
		if(	( m_pBuffer->PartitionEntry[dwIndex].RecognizedPartition ) &&
			( m_pBuffer->PartitionEntry[dwIndex].StartingOffset.QuadPart == llPartStartOffset ) )
		{
			wPartitionType = ( dwIndex < 4 ) ? PT_Primary : PT_InExtendedPartition;
			memcpy(&partInfo, &(m_pBuffer->PartitionEntry[dwIndex]), sizeof(PARTITION_INFORMATION) );
			return TRUE;
		}
	}
		
	// The partition was not found
	AddError( strErrors, IDS_ERR_PARTITION_NOT_FOUND, FALSE );

	return FALSE;

	MY_CATCH_AND_THROW
}

/*
Public method:		DeletePartition

Purpose:			Deletes a partition of the disk ( m_dwDiskNumber )

Parameters:			[IN] LONGLONG llPartStartOffset
						Offset of the partition

Return value:		TRUE	if the deletion succeeded							
*/

BOOL CDiskMap::DeletePartition( 
						LONGLONG llPartStartOffset )
{
	MY_TRY

	ASSERT( m_dwDiskNumber >= 0 );

	CString strErrors;
	BOOL	bMissingDisk;
	
	if( !m_bLoaded )
	{
		if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		{
			if( bMissingDisk )
				strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
			AfxMessageBox( strErrors, MB_ICONSTOP );
			return FALSE;
		}
	}

	// Retrieve the indexes in the partition table of the partition itself and of its parent extended partition ( if any )
	DWORD dwPartIndex, dwParentIndex, dwNextIndex;
	if( !SearchForPartitionInExtendedPartition( llPartStartOffset, MAXDWORD, 0, dwNextIndex, dwPartIndex, dwParentIndex ) )
	{
		// The partition was not found
		AfxMessageBox( IDS_ERR_PARTITION_NOT_FOUND, MB_ICONSTOP );
		return FALSE;
	}

	// Delete the partition from its parent table
	DeletePartitionFromTable( dwPartIndex );

	// Update / Delete the parent extended partition ( depends on what's left inside it )
	if( dwParentIndex != MAXDWORD )     // The partition was a member of an extended partition
		UpdateExtendedPartitionAfterMemberDeletion( dwParentIndex, ((DWORD)( dwPartIndex / 4 ))*4 ); 

	// Time to save on the disk
	if( !SaveDiskInfo( strErrors, bMissingDisk ) )
	{
		if( bMissingDisk )
			strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
		AfxMessageBox( strErrors, MB_ICONSTOP );
		return FALSE;
	}

	return TRUE;

	MY_CATCH_AND_THROW
}

/*
Public method:		DeleteExtendedPartition

Purpose:			Delete an extended partition of the disk ( m_dwDiskNumber ).
					The extended partition should not be contained by another extended partition

Parameters:			[IN] LONGLONG llPartStartOffset
						Offset of the extended partition

Return value:		TRUE	if the deletion succeeded							
*/

BOOL CDiskMap::DeleteExtendedPartition(  
						LONGLONG llPartStartOffset )
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	CString strErrors;
	BOOL	bMissingDisk;
	
	if( !m_bLoaded )
	{
		if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		{
			if( bMissingDisk )
				strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
			AfxMessageBox( strErrors, MB_ICONSTOP );
			return FALSE;
		}
	}

	// The real offset of the extended partition is one track before the offset of the free space
	llPartStartOffset -= m_llTrackSize;
	
	// Search the extended partition in the disk 4 slots table
	DWORD dwMemberCount = ( 4 <= m_pBuffer->PartitionCount ) ? 4 : m_pBuffer->PartitionCount;
	for( UINT i = 0; i < dwMemberCount; i++ )
	{
		PARTITION_INFORMATION* pPart = &(m_pBuffer->PartitionEntry[i]);

		if( IsContainerPartition( pPart->PartitionType ) && ( pPart->StartingOffset.QuadPart == llPartStartOffset ) )
			break;
	}

	if( i == dwMemberCount )
	{
		// Partition not found
		AfxMessageBox( IDS_ERR_PARTITION_NOT_FOUND, MB_ICONSTOP );
		return FALSE;
	}

	DeletePartitionFromTable(i);

	// Time to save on the disk
	if( !SaveDiskInfo( strErrors, bMissingDisk ) )
	{
		if( bMissingDisk )
			strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
		AfxMessageBox( strErrors, MB_ICONSTOP );
		return FALSE;
	}

	return TRUE;

	MY_CATCH_AND_THROW
}

/*
Public method:		CreatePartition

Purpose:			Create a partition on the disk ( m_dwDiskNumber )

Parameters:			[IN] LONGLONG llPartStartOffset
						Aproximative starting offset of the partition ( this might be modified due to 
						the creation of an extra container for this partition
					[IN] LONGLONG llPartSize
						Aproximative size of the partition ( this should be rounded-up to the next cylinder border )
					[OUT] LONGLONG& llExactPartStartOffset
						The exact starting offset of the partition

Return value:		TRUE	if the creation succeeded							
*/

BOOL CDiskMap::CreatePartition( 
						LONGLONG	llPartStartOffset,
						LONGLONG	llPartSize,
						LONGLONG&	llExactPartStartOffset)
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	CString strErrors;
	BOOL	bMissingDisk;
	
	if( !m_bLoaded )
	{
		if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		{
			if( bMissingDisk )
				strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
			AfxMessageBox( strErrors, MB_ICONSTOP );
			return FALSE;
		}
	}

	LONGLONG	llPartEndOffset = GetCylinderBorderAfter( llPartStartOffset + llPartSize );
	LONGLONG	llPartTrueSize = llPartEndOffset - llPartStartOffset;
	LONGLONG	llFreeSpaceStartOffset;
	LONGLONG	llFreeSpaceEndOffset;
	BOOL		bAtBeginningOfExtendedPartition;
	DWORD		dwNewPartIndex;
	
	if( !SearchForFreeSpaceInExtendedPartition( llPartStartOffset, llPartTrueSize, 0, m_llDiskSize, 0,
												llFreeSpaceStartOffset, llFreeSpaceEndOffset,
												bAtBeginningOfExtendedPartition, dwNewPartIndex) )
	{
		// The free space was not found
		AfxMessageBox( IDS_ERR_FREE_SPACE_NOT_FOUND, MB_ICONSTOP );
		return FALSE;
	}

	// If the partition must be created inside a container and it can't be created at the beginning of
	// this container then a new container must be created for it
	BOOL bCreateContainer = ( ( dwNewPartIndex >= 4 ) && !bAtBeginningOfExtendedPartition );

	if( !AddPartitionToTable( llPartStartOffset, llPartTrueSize, dwNewPartIndex, bCreateContainer, TRUE ) )
		return FALSE;

	if( bCreateContainer )
		llExactPartStartOffset = llPartStartOffset + m_llTrackSize;
	else
		llExactPartStartOffset = llPartStartOffset;
	
	// Time to save on the disk
	if( !SaveDiskInfo( strErrors, bMissingDisk ) )
	{
		if( bMissingDisk )
			strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
		AfxMessageBox( strErrors, MB_ICONSTOP );
		return FALSE;
	}

	return TRUE;

	MY_CATCH_AND_THROW
}


/*
Public method:		CreateExtendedPartition

Purpose:			Create an extended partition on the disk ( m_dwDiskNumber )

Parameters:			[IN] LONGLONG llPartStartOffset
						Offset of the partition
					[IN] LONGLONG llPartSize
						Estimate size of the partition ( this should be rounded-up to the next cylinder border )
					[OUT] LONGLONG& llNewFreeSpaceOffset
						The starting offset of the newly created free space inside the new empty extended partition
						Usually it is the starting offset of the extended partition + a track size


Return value:		TRUE	if the creation succeeded							
*/

BOOL CDiskMap::CreateExtendedPartition( 
						LONGLONG	llPartStartOffset,
						LONGLONG	llPartSize,
						LONGLONG&	llNewFreeSpaceOffset )
{
	MY_TRY
	
	ASSERT( m_dwDiskNumber >= 0 );

	CString strErrors;
	BOOL	bMissingDisk;
	
	if( !m_bLoaded )
	{
		if( !LoadDiskInfo( strErrors, bMissingDisk ) )
		{
			if( bMissingDisk )
				strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
			AfxMessageBox( strErrors, MB_ICONSTOP );
			return FALSE;
		}
	}

	LONGLONG	llPartEndOffset = GetCylinderBorderAfter( llPartStartOffset + llPartSize );
	LONGLONG	llPartTrueSize = llPartEndOffset - llPartStartOffset;
	LONGLONG	llFreeSpaceStartOffset;
	LONGLONG	llFreeSpaceEndOffset;
	BOOL		bAtBeginningOfExtendedPartition;
	DWORD		dwNewPartIndex;
	
	if( !SearchForFreeSpaceInExtendedPartition( llPartStartOffset, llPartTrueSize, 0, m_llDiskSize, 0,
												llFreeSpaceStartOffset, llFreeSpaceEndOffset, 
												bAtBeginningOfExtendedPartition, dwNewPartIndex) ||
		( dwNewPartIndex >= 4 ) )
	{
		// The free space was not found or was found in another extended partition
		AfxMessageBox( IDS_ERR_FREE_SPACE_NOT_FOUND, MB_ICONSTOP );
		return FALSE;
	}

	if( !AddPartitionToTable( llPartStartOffset, llPartTrueSize, dwNewPartIndex, TRUE, FALSE ) )
		return FALSE;
	
	// Time to save on the disk
	if( !SaveDiskInfo( strErrors, bMissingDisk ) )
	{
		if( bMissingDisk )
			strErrors.Format( IDS_ERR_MISSING_DISK, m_dwDiskNumber );
		AfxMessageBox( strErrors, MB_ICONSTOP );
		return FALSE;
	}
	
	llNewFreeSpaceOffset = llPartStartOffset + m_llTrackSize;

	return TRUE;

	MY_CATCH_AND_THROW
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Protected methods

/*
Protected method:	GetNextIndex

Purpose:			Given an extended partition table index in m_pBuffer->PartitionInfo, scan recursively  
					the extended partition tree and find the chunk of m_pBuffer->PartitionInfo filled with
					members of the extended partition
					Then return the next index. This index is the index of the following extended partition 
					from the same ( or superior ) level as the given extended partition
					
Parameters:			[IN] DWORD dwTableIndex
						Index in m_pBuffer->PartitionInfo of the extended partition
						( 0 for the whole disk );

Return value:		Index in m_pBuffer->PartitionEntry of the next extended partition table on the same level
*/

DWORD CDiskMap::GetNextIndex( DWORD dwTableIndex )
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( dwTableIndex >= 0 );
	ASSERT( dwTableIndex%4 == 0 );

	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );
	
	DWORD dwNextIndex = dwTableIndex + 4; 
	
	// 1. Scan the partition table. Compute the end offset for every member. For container members retrieve all
	// their free spaces.
	for( UINT i = 0; ( i < 4 ) && ( dwTableIndex + i < m_pBuffer->PartitionCount ); i++ )
	{
		if( IsContainerPartition( pTable[i].PartitionType ) )
			dwNextIndex = GetNextIndex( dwNextIndex );	
	}
	
	return dwNextIndex;

	MY_CATCH_AND_THROW
}

/*
Protected method:	GetExtendedPartitionFreeSpaces

Purpose:			Search recursively for free spaces inside an extended partition given its starting offset, 
					size and partitions table index inside m_pBuffer
					This method may be called also to get the free spaces of the whole disk.

					For every found free space create a CFreeSpace instance and add it to arrFreeSpaces
					
Parameters:			[IN] LONGLONG llExtPartStartOffset
						The start offset of the extended partition ( 0 for the whole disk )
					[IN] LONGLONG llExtPartEndOffset
						The end offset of the extended partition ( m_llDiskSize for the whole disk )
					[IN] DWORD dwTableIndex
						Index in m_pBuffer->PartitionEntry of the partition table of the extended partition
						( 0 for the whole disk )
					[OUT] DWORD& dwNextIndex
						Index in m_pBuffer->PartitionEntry of the next extended partition table
					[OUT] CObArray& arrFreeSpaces
						Array of CFreeSpaceData containing all free spaces found in this extended partition (disk)
					[IN] CItemData* pParentData
						The items' parent data. It will fill the m_pParentData member of all new
						CFreeSpaceData instances

Return value:		-
*/

void CDiskMap::GetExtendedPartitionFreeSpaces( 
						LONGLONG llExtPartStartOffset, 
						LONGLONG llExtPartEndOffset, 
						DWORD dwTableIndex,
						DWORD& dwNextIndex,
						CObArray& arrFreeSpaces,
						CItemData* pParentData /* = NULL */)
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( dwTableIndex >= 0 );
	ASSERT( dwTableIndex%4 == 0 );
	
	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );
	CObArray				arrPartFreeSpaces[4];
	LONGLONG				arrPartEndOffset[4];
	PARTITION_INFORMATION*	pPart;

	DWORD	dwMemberCount = ( dwTableIndex + 4 <= m_pBuffer->PartitionCount ) ? 4 : m_pBuffer->PartitionCount - dwTableIndex;
	
	// 1. Order the members by starting offset ( they may be not ordered in pTable )
	
	DWORD arrOrder[4];
	DWORD dwPartitionCount = SortPartitionsByOffset( pTable, dwMemberCount, arrOrder );
	ASSERT( dwPartitionCount <= dwMemberCount );

	// 2. Scan the partition table. For container members retrieve all their free spaces.
	
	dwNextIndex = dwTableIndex + 4;
	DWORD dwExtendedPartitionCountOnLevel = 0;
	
	for( UINT i = 0; i < dwMemberCount; i++ )
	{
		pPart = &(pTable[i]);
		if( pPart->PartitionType == 0 )
			continue;

		if( IsContainerPartition( pPart->PartitionType ) )
		{
			if( dwTableIndex == 0 )
			{
				// This is a root container partition. Its superior limit is given by its size
				arrPartEndOffset[i] = pPart->StartingOffset.QuadPart + pPart->PartitionLength.QuadPart;
			}
			else
			{
				// This is a container inside another container. Its superior limit is given by the starting offset
				// of the next partition ( as position ) or by the end of its parent container
				for( UINT j = 0; j < dwPartitionCount; j++ )
				{
					if( arrOrder[j] == i )
						break;
				}
				ASSERT( j < dwPartitionCount );
				if( j < dwPartitionCount - 1 )
					arrPartEndOffset[i] = pTable[ arrOrder[j+1] ].StartingOffset.QuadPart;
				else
					arrPartEndOffset[i] = llExtPartEndOffset;				
			}
			
			GetExtendedPartitionFreeSpaces( 
							pPart->StartingOffset.QuadPart, arrPartEndOffset[i], 
							dwNextIndex, dwNextIndex, arrPartFreeSpaces[i], pParentData );	

			dwExtendedPartitionCountOnLevel++;
		}
		else
			arrPartEndOffset[i] = pPart->StartingOffset.QuadPart + pPart->PartitionLength.QuadPart;
	}

	// 3. Now retrieve all free spaces inside this extended partition and add them to arrFreeSpaces
	// together with the free spaces of the container members. arrFreeSpaces must be sorted by starting offset
	
	// The first track of the disk / extended partition is reserved
	LONGLONG	llFreeSpaceStartOffset = llExtPartStartOffset + m_llTrackSize;
	LONGLONG	llFreeSpaceEndOffset;
	for( i = 0; i <= dwPartitionCount ; i++ )
	{
		// The end of a free space could be the starting offset of the next member or the end of the extended 
		// partition
		if( i == dwPartitionCount )
			llFreeSpaceEndOffset = llExtPartEndOffset;
		else
		{
			pPart = &(pTable[ arrOrder[i] ]);
			// Always take the greatest cylinder border less than or equal with the starting offset of the next member
			llFreeSpaceEndOffset = GetCylinderBorderBefore(pPart->StartingOffset.QuadPart);
		}
		
		// A free space must be at least 1 cylinder size long
		if( llFreeSpaceStartOffset + m_llCylinderSize <= llFreeSpaceEndOffset )
		{
			FREE_SPACE_TYPE wFreeSpaceType;
			if( dwTableIndex == 0 )				// This is a free space between primary partitions
				wFreeSpaceType = FST_Primary;
			else if( dwPartitionCount > 0 )		// This is a free space inside a non-empty extended partition
				wFreeSpaceType = FST_InExtendedPartition;
			else								// This is an empty extended partition
				wFreeSpaceType = FST_EmptyExtendedPartition;
			
			// We found a free space !!!
			CFreeSpaceData* pData = new CFreeSpaceData(
														m_dwDiskNumber, 
														m_pBuffer->Signature, 
														llFreeSpaceStartOffset,
														llFreeSpaceEndOffset - llFreeSpaceStartOffset,
														wFreeSpaceType,
														m_llCylinderSize,
														dwPartitionCount - dwExtendedPartitionCountOnLevel,
														dwExtendedPartitionCountOnLevel,
														pParentData
														);
			CString strMemberErrors;
			pData->ReadItemInfo( strMemberErrors );
			ASSERT( strMemberErrors.IsEmpty() ); 
			// Add the structure to the members' data array
			arrFreeSpaces.Add(pData);
		}			

		if( i != dwPartitionCount )
		{
			// The starting offset of the next free space could be the end offset of the current member		
			// Always take the lower cylinder border greater or equal with this value
			llFreeSpaceStartOffset =	GetCylinderBorderAfter( arrPartEndOffset[ arrOrder[i] ] );
			if( IsContainerPartition( pPart->PartitionType ) )
				arrFreeSpaces.Append( arrPartFreeSpaces[ arrOrder[i] ] );
		}
		
	}

	return;

	MY_CATCH_AND_THROW
}

/*
Protected method:	SortPartitionsByOffset

Purpose:			Sort a partition table by starting offset without actually changing the table
					
Parameters:			[IN] PARTITION_INFORMATION* arrTable
						Array of partitions ( inside m_pBuffer )
					[IN] DWORD
						The size of the array
					[OUT] DWORD* arrOrder
						Array of indexes in arrTable of partitions sorted by starting offset
						The null partitions are not considered at all

Return value:		The number of not null sorted partitions
*/

DWORD CDiskMap::SortPartitionsByOffset( 
						PARTITION_INFORMATION* arrTable, 
						DWORD dwSize, 
						DWORD* arrOrder )
{
	MY_TRY
	
	DWORD dwOrderedSize = 0;
	for( DWORD i = 0; i < dwSize; i++ )
	{
		// Just ignore null partitions
		if( arrTable[i].PartitionType == 0 )
			continue;

		for( DWORD j = 0; j < dwOrderedSize; j++ )
		{
			if( arrTable[i].StartingOffset.QuadPart < arrTable[arrOrder[j]].StartingOffset.QuadPart )
				break;
		}
		
		for( DWORD k = dwOrderedSize; k > j; k-- )
			arrOrder[k] = arrOrder[k-1];

		arrOrder[j] = i;
		dwOrderedSize++;
	}
	return dwOrderedSize;

	MY_CATCH_AND_THROW
}

/*
Protected method:	GetCylinderBorderBefore

Purpose:			Get the greatest cylinder border less than or equal with an offset
					
Parameters:			[IN] LONGLONG llOffset
						The offset
					
Return value:		The greatest cylinder border less than or equal with llOffset
*/

LONGLONG CDiskMap::GetCylinderBorderBefore( LONGLONG llOffset )
{	
	// This is the most common case so treat it separately
	if( llOffset%m_llCylinderSize == 0 )
		return llOffset;
	
	//And this the generic formula
	return ((LONGLONG)(llOffset / m_llCylinderSize)) * m_llCylinderSize;
}

/*
Protected method:	GetCylinderBorderAfter

Purpose:			Get the lowest cylinder border greater than or equal with an offset
					
Parameters:			[IN] LONGLONG llOffset
						The offset
					
Return value:		The lowest cylinder border greater than or equal with llOffset
*/

LONGLONG CDiskMap::GetCylinderBorderAfter( LONGLONG llOffset )
{
	// This is the most common case so treat it separately
	if( llOffset%m_llCylinderSize == 0 )
		return llOffset;

	// And this is the generic formula
	return ((LONGLONG)((llOffset + m_llCylinderSize - 1) / m_llCylinderSize)) * m_llCylinderSize;
}

/*
Protected method:	SearchForPartitionInExtendedPartition

Purpose:			Search recursively for a partition inside an extended partition
					This method may be called also to search for a partition inside the whole disk.
					
Parameters:			[IN] LONGLONG llPartOffset
						The start offset of the partition we are looking for
					[IN] DWORD dwExtPartIndex
						Index in m_pBuffer->PartitionEntry of the extended partition
						( MAXDWORD for the whole disk because we don't have -1 available )
					[IN] DWORD dwTableIndex
						Index in m_pBuffer->PartitionEntry of the partition table of the extended partition
						( 0 for the whole disk )
					[OUT] DWORD& dwNextIndex
						Index in m_pBuffer->PartitionEntry of the next extended partition table
					[OUT] DWORD& dwPartIndex
						Index in m_pBuffer->PartitionEntry of the partition we are looking for ( if found )
					[OUT] DWORD& dwParentIndex
						Index in m_pBuffer->PartitionEntry of the parent partition ( if partition found )

Return value:		TRUE if the partition was found inside this extended partition ( disk )
*/

BOOL CDiskMap::SearchForPartitionInExtendedPartition( 
						LONGLONG	llPartOffset,
						DWORD		dwExtPartIndex,	
						DWORD		dwTableIndex,
						DWORD&		dwNextIndex,
						DWORD&		dwPartIndex, 
						DWORD&		dwParentIndex )
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( dwTableIndex >= 0 );
	ASSERT( dwTableIndex%4 == 0 );
	
	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );
	
	dwNextIndex = dwTableIndex + 4; 
	
	for( UINT i = 0; ( i < 4 ) && ( dwTableIndex + i < m_pBuffer->PartitionCount ) ; i++ )
	{
		PARTITION_INFORMATION* pPart = &(pTable[i]);
		if( pPart->PartitionType == 0 )
			continue;

		if( IsContainerPartition( pPart->PartitionType ) )
		{
			// Search in depth
			if( SearchForPartitionInExtendedPartition(	llPartOffset, dwTableIndex + i, dwNextIndex, 
														dwNextIndex, dwPartIndex, dwParentIndex ) )
				return TRUE;
		}
		else
		{
			if( pPart->StartingOffset.QuadPart == llPartOffset )
			{
				// This is my partition
				dwPartIndex = dwTableIndex + i;
				dwParentIndex = dwExtPartIndex;
				return TRUE;
			}
		}
	}

	return FALSE;

	MY_CATCH_AND_THROW
}

/*
Protected method:	DeletePartitionFromTable

Purpose:			Delete a partition from m_pBuffer->PartitionInfo table.
					
Parameters:			[IN] DWORD dwPartIndex
						The index of the partition in m_pBuffer->PartitionInfo
					

Return value:		-
*/

void CDiskMap::DeletePartitionFromTable( DWORD dwPartIndex )
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( ( dwPartIndex >= 0 ) && ( dwPartIndex < m_pBuffer->PartitionCount ) );
	
	// Get the 4 slots table of its parent
	DWORD dwTableIndex = ((DWORD)( dwPartIndex / 4 )) * 4;
	DWORD dwEndIndex = dwTableIndex + 3;
	if( dwEndIndex >= m_pBuffer->PartitionCount )
		dwEndIndex = m_pBuffer->PartitionCount - 1;
	
	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );
	
	// Check if the given partition is a container partition
	BOOL bContainer = IsContainerPartition( m_pBuffer->PartitionEntry[dwPartIndex].PartitionType );

	// If container partition get the index in m_pBuffer->PartitionEntry for the 4 slots table of that partition
	DWORD dwPartTableIndex = dwTableIndex + 4;
	DWORD dwPartNextIndex;
	DWORD dwShift = 0;
	if( bContainer )
	{
		for( DWORD i = dwTableIndex; i < dwPartIndex; i++ )
		{
			if( IsContainerPartition( m_pBuffer->PartitionEntry[i].PartitionType ) )
				dwPartTableIndex = GetNextIndex( dwPartTableIndex );
		}

		dwPartNextIndex = GetNextIndex( dwPartTableIndex );
		dwShift = dwPartNextIndex - dwPartTableIndex;
	}
	
	// Move all following partitions one position to the left in the 4 slots table
	for( DWORD i = dwPartIndex; i < dwEndIndex;  i++ )
	{
		memcpy( &(m_pBuffer->PartitionEntry[i]), &(m_pBuffer->PartitionEntry[i+1]), sizeof( PARTITION_INFORMATION ) ); 
		m_pBuffer->PartitionEntry[i].RewritePartition = TRUE;
	}


	// Fill the last entry in the 4 slots table with zero
	memset( &(m_pBuffer->PartitionEntry[ dwEndIndex ]), 0, sizeof( PARTITION_INFORMATION ) ); 
	m_pBuffer->PartitionEntry[dwEndIndex].RewritePartition = TRUE;
	
	// If container partition remove from m_pBuffer->PartitionEntry all entries belonging to this container partition
	if( bContainer && ( dwShift > 0 ) )
	{
		for( DWORD i = dwPartTableIndex; i < m_pBuffer->PartitionCount - dwShift; i++ )
		{
			memcpy( &(m_pBuffer->PartitionEntry[i]), &(m_pBuffer->PartitionEntry[i+dwShift]), sizeof( PARTITION_INFORMATION ) ); 
			m_pBuffer->PartitionEntry[i].RewritePartition = TRUE;
		}
		m_pBuffer->PartitionCount -= dwShift;
	}

	MY_CATCH_AND_THROW
}

/*
Protected method:	UpdateExtendedPartitionAfterMemberDeletion

Purpose:			Update / Delete an extended partition after one of its members was deleted
					If the extended partition remains empty then it must be deleted
					If only one member is left and this member is an extended partition too then replace the
					extended partition with its member ( promote the member on the superior level )
					All other situations are unlikely to happen so I don't treat them here
					
Parameters:			[IN] DWORD dwPartIndex
						The index in m_pBuffer->PartitionInfo of the partition
					[IN] DWORD
						The index in m_pBuffer->PartitionInfo of the 4 slots table of the partition
					
Return value:		-
*/

void CDiskMap::UpdateExtendedPartitionAfterMemberDeletion( 
					DWORD dwPartIndex, 
					DWORD dwTableIndex )
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( ( dwPartIndex >= 0 ) && ( dwPartIndex < m_pBuffer->PartitionCount ) );
	ASSERT( dwTableIndex >= 0 );
	ASSERT( dwTableIndex%4 == 0 );

	// If the extended partition is member of the whole disk then don't touch it
	if( dwPartIndex < 4 )
		return;

	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );

	// Order the members by starting offset
	DWORD arrOrder[4];
	DWORD dwMemberCount = ( dwTableIndex + 4 <= m_pBuffer->PartitionCount ) ? 4 : m_pBuffer->PartitionCount - dwTableIndex;
	dwMemberCount = SortPartitionsByOffset( pTable, dwMemberCount, arrOrder );

	if( dwMemberCount == 0 )
	{
		// The extended partition is empty so delete it
		DeletePartitionFromTable( dwPartIndex );
		return;
	}
	else if( dwMemberCount == 1 )
	{
		PARTITION_INFORMATION* pPart = &(pTable[ arrOrder[0] ]);
		if( IsContainerPartition( pPart->PartitionType ) )
		{
			// Replace the parent extended partition with this extended partition
			memcpy( &(m_pBuffer->PartitionEntry[dwPartIndex]), pPart, sizeof(PARTITION_INFORMATION) );
			m_pBuffer->PartitionEntry[dwPartIndex].RewritePartition = TRUE;
			// Remove the 4 slots table of the parent extended partition
			for( DWORD i = dwTableIndex; ( i + 4 < m_pBuffer->PartitionCount ); i++ )
			{
				memcpy( &(m_pBuffer->PartitionEntry[i]), &(m_pBuffer->PartitionEntry[i+4]), sizeof( PARTITION_INFORMATION ) ); 
				m_pBuffer->PartitionEntry[i].RewritePartition = TRUE;
			}
			m_pBuffer->PartitionCount -= 4;
			return;
		}
	}

	MY_CATCH_AND_THROW
}

/*
Protected method:	SearchForFreeSpaceInExtendedPartition

Purpose:			Given an offset and size search recursively for an appropriate free space
					inside an extended partition ( or the whole disk ).
					Return the start and end offset of the whole free space and the index in the partition table 
					where a new partition having the given offset and size might be inserted.
					
Parameters:			[IN] LONGLONG llOffset
						The start offset of the space we are looking for
					[IN] LONGLONG llSize
						The size of the space
					[IN] LONGLONG llExtPartStartOffset
						The start offset of the extended partition ( 0 for the whole disk )
					[IN] LONGLONG llExtPartEndOffset
						The end offset of the extended partition ( m_llDiskSize for the whole disk )
					[IN] DWORD dwTableIndex
						Index in m_pBuffer->PartitionEntry of the partition table of the extended partition
						( 0 for the whole disk )
					[OUT] LONGLONG& llFreeSpaceStartOffset
						The start offset of the appropriate free space
					[OUT] LONGLONG& llFreeSpaceEndOffset
						The end offset of the appropriate end space
					[OUT] BOOL &bAtBeginningOfExtendedPartition
						Is the free space at the beginning of the extended partition?
					[OUT] DWORD& dwNewPartIndex
						The index in m_pBuffer->PartitionEntry where a new partition having the given offset 
						and size may be inserted


Return value:		TRUE if a free space was found
*/

BOOL CDiskMap::SearchForFreeSpaceInExtendedPartition(
						LONGLONG	llOffset,
						LONGLONG	llSize,
						LONGLONG	llExtPartStartOffset, 
						LONGLONG	llExtPartEndOffset, 
						DWORD		dwTableIndex,
						LONGLONG&	llFreeSpaceStartOffset,
						LONGLONG&	llFreeSpaceEndOffset,
						BOOL		&bAtBeginningOfExtendedPartition,
						DWORD&		dwNewPartIndex)
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( dwTableIndex >= 0 );
	ASSERT( dwTableIndex%4 == 0 );
	
	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );
	LONGLONG				arrPartEndOffset[4];
	PARTITION_INFORMATION*	pPart;

	DWORD	dwMemberCount = ( dwTableIndex + 4 <= m_pBuffer->PartitionCount ) ? 4 : m_pBuffer->PartitionCount - dwTableIndex;
	
	// 1. Order the members by starting offset ( they may be not ordered in pTable )
	
	DWORD arrOrder[4];
	DWORD dwPartitionCount = SortPartitionsByOffset( pTable, dwMemberCount, arrOrder );
	ASSERT( dwPartitionCount <= dwMemberCount );

	// 2. Scan the partition table. For container members try to create the partition inside them
	
	DWORD dwNextIndex = dwTableIndex + 4; 
	
	for( UINT i = 0; i < dwMemberCount; i++ )
	{
		pPart = &(pTable[i]);
		if( pPart->PartitionType == 0 )
			continue;

		if( IsContainerPartition( pPart->PartitionType ) )
		{
			if( dwTableIndex == 0 )
			{
				// This is a root container partition. Its superior limit is given by its size
				arrPartEndOffset[i] = pPart->StartingOffset.QuadPart + pPart->PartitionLength.QuadPart;
			}
			else
			{
				// This is a container inside another container. Its superior limit is given by the starting offset
				// of the next partition ( as position ) or by the end of its parent container
				for( UINT j = 0; j < dwPartitionCount; j++ )
				{
					if( arrOrder[j] == i )
						break;
				}
				ASSERT( j < dwPartitionCount );
				if( j < dwPartitionCount - 1 )
					arrPartEndOffset[i] = pTable[ arrOrder[j+1] ].StartingOffset.QuadPart;
				else
					arrPartEndOffset[i] = llExtPartEndOffset;				
			}
			
			if( ( llOffset >= pPart->StartingOffset.QuadPart ) &&
				( llOffset + llSize <= arrPartEndOffset[i] ) )
			{
				// Create the partition inside this container
				return SearchForFreeSpaceInExtendedPartition( llOffset, llSize,
							pPart->StartingOffset.QuadPart, arrPartEndOffset[i], dwNextIndex,
							llFreeSpaceStartOffset, llFreeSpaceEndOffset, bAtBeginningOfExtendedPartition, dwNewPartIndex );
			}
			else
				dwNextIndex = GetNextIndex(dwNextIndex);
		}
		else
			arrPartEndOffset[i] = pPart->StartingOffset.QuadPart + pPart->PartitionLength.QuadPart;
	}

	// 3. We must search for our free space among the free spaces between the members of this extended partition !!!

	// The first track of the disk / extended partition is reserved
	llFreeSpaceStartOffset = llExtPartStartOffset + m_llTrackSize;
	
	for( i = 0 ; i <= dwPartitionCount ; i++ )
	{
		// The end of a free space could be the starting offset of the next member or the end of the extended 
		// partition
		if( i == dwPartitionCount )
			llFreeSpaceEndOffset = llExtPartEndOffset;
		else
		{
			pPart = &(pTable[ arrOrder[i] ]);
			// Always take the greatest cylinder border less than or equal with the starting offset of the next member
			llFreeSpaceEndOffset = GetCylinderBorderBefore(pPart->StartingOffset.QuadPart);
		}
		
		// A free space must be at least 1 cylinder size long
		// Check also if the new partition would match in the free space
		if( ( llFreeSpaceStartOffset + m_llCylinderSize <= llFreeSpaceEndOffset ) &&
			( llOffset >= llFreeSpaceStartOffset) &&
			( llOffset + llSize <= llFreeSpaceEndOffset) )
		{
			// We found the appropriate free space but there is still a problem
			// What if the 4 slots table is already full?

			if( dwPartitionCount >= dwMemberCount )
			{
				AfxMessageBox( IDS_ERR_PARTITION_TABLE_FULL, MB_ICONSTOP );
				return FALSE;
			}
			bAtBeginningOfExtendedPartition = ( llFreeSpaceStartOffset == llExtPartStartOffset + m_llTrackSize );
			dwNewPartIndex = dwTableIndex + i;	
			return TRUE;
		}
		
		if( i != dwPartitionCount )
		{
			// The starting offset of the next free space could be the end offset of the current member		
			// Always take the lower cylinder border greater or equal with this value
			llFreeSpaceStartOffset =	GetCylinderBorderAfter( arrPartEndOffset[ arrOrder[i] ] );			
		}
	}

	// We didn't find a matching free space

	return FALSE;

	MY_CATCH_AND_THROW
}

/*
Protected method:	AddPartitionToTable

Purpose:			Add a new container partition AND/OR a new non-container partition to m_pBuffer->PartitionInfo 
					table and make all necessary changes to the partition table
					
Parameters:			[IN] LONGLONG llPartStartOffset
						The starting offset of the new partition
					[IN] LONGLONG llPartTrueSize
						The size of the new partition
					[IN] DWORD dwNewPartIndex
						The index of the new partition in m_pBuffer->PartitionInfo
					[IN] BOOL bCreateContainer
						Should we create a new extended partition?
					[IN] BOOL bCreateNonContainer
						Should we create a new non-container partition?

					Note: bCreateContainer and bNonCreateContainer can be both TRUE. Then a container partition
					is created first and a non-container partition is created inside this container
					These BOOL values cannot be both FALSE.
					

Return value:		TRUE if the partition table was modified successfully
*/

BOOL CDiskMap::AddPartitionToTable( 
						LONGLONG	llPartStartOffset, 
						LONGLONG	llPartSize, 
						DWORD		dwNewPartIndex, 
						BOOL		bCreateContainer,
						BOOL		bCreateNonContainer)
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( ( dwNewPartIndex >= 0 ) && ( dwNewPartIndex < m_pBuffer->PartitionCount ) );
	
	// We can create a new container partition, a new non-container partition or a 
	// new non-container partition inside a new container partition
	ASSERT( bCreateContainer || bCreateNonContainer );
	
	// Get the 4 slots table of its parent
	DWORD dwTableIndex = ((DWORD)( dwNewPartIndex / 4 )) * 4;
	DWORD dwMemberCount = ( dwTableIndex + 4 <= m_pBuffer->PartitionCount ) ? 4 : m_pBuffer->PartitionCount - dwTableIndex;
	
	PARTITION_INFORMATION*	pTable = &( m_pBuffer->PartitionEntry[dwTableIndex] );

	// 1. Get the chunks of m_pBuffer->PartitionEntry allocated for every container member of the extended partition
	DWORD arrStartIndex[4];
	DWORD arrEndIndex[4];
	DWORD dwNextIndex = dwTableIndex + 4;

	for( DWORD i = 0; i < dwMemberCount; i++ )
	{
		if( IsContainerPartition( pTable[i].PartitionType ) )
		{
			arrStartIndex[i] = dwNextIndex;
			arrEndIndex[i] = dwNextIndex = GetNextIndex(dwNextIndex);
		}
	}
	
	// 2. Sort the members of the extended partition by starting offset
	DWORD arrOrder[4];
	DWORD dwPartitionCount = SortPartitionsByOffset( pTable, dwMemberCount, arrOrder );	

	// There should be another free slot in the table. SearchForFreeSpaceInExtendedPartition should take care of this
	ASSERT( dwPartitionCount < dwMemberCount );
	
	
	// 3. Allocate a new buffer. We must be prepared to add some extra 4 slots to the partition table
	PDRIVE_LAYOUT_INFORMATION pNewBuffer = (PDRIVE_LAYOUT_INFORMATION)LocalAlloc( 0, 
		sizeof(DRIVE_LAYOUT_INFORMATION) + ( m_pBuffer->PartitionCount + 4 )*sizeof(PARTITION_INFORMATION) );
	if( pNewBuffer == NULL )
	{
		AfxMessageBox( IDS_ERR_ALLOCATION, MB_ICONSTOP);
		return FALSE;
	}

	// The drive info and the first m_dwTableIndex partition entries will not be changed in the new buffer
	memcpy( pNewBuffer, m_pBuffer, sizeof(DRIVE_LAYOUT_INFORMATION) + dwTableIndex*sizeof(PARTITION_INFORMATION) );

	// 4. Add the first ( dwNewPartIndex - dwTableIndex ) members of the extended partition to the new buffer 
	// in the starting offset order. Add also their chunks of m_pBuffer->PartitionEntry
	
	PARTITION_INFORMATION*	pNewTable = &( pNewBuffer->PartitionEntry[dwTableIndex] );	
	UINT	iNext;								// Index in pTable
	UINT	iNewNext = 0;						// Index in pNewTable
	DWORD	dwNewNextIndex = dwTableIndex + 4;	// Index in pNewBuffer->PartitionEntry of the next free chunk for an extended partition

	for( iNext = 0, iNewNext = 0; iNext < dwNewPartIndex - dwTableIndex; iNext++, iNewNext++ )
	{
		DWORD iOrder = arrOrder[iNext];
		memcpy( &(pNewTable[iNewNext]), &(pTable[iOrder]), sizeof(PARTITION_INFORMATION));
		if( iNewNext != iOrder )
			pNewTable[iNewNext].RewritePartition = TRUE;

		if( IsContainerPartition( pNewTable[iNewNext].PartitionType ) )
		{
			memcpy( &(pNewBuffer->PartitionEntry[dwNewNextIndex]), 
					&(m_pBuffer->PartitionEntry[ arrStartIndex[iOrder] ]), 
					( arrEndIndex[iOrder] - arrStartIndex[iOrder] ) * sizeof(PARTITION_INFORMATION));
		
			if( arrStartIndex[iOrder] != dwNewNextIndex )
			{
				for( DWORD j = arrStartIndex[iOrder]; j < arrEndIndex[iOrder]; j++ )
					pNewBuffer->PartitionEntry[dwNewNextIndex++].RewritePartition = TRUE;
			}
			else
				dwNewNextIndex += ( arrEndIndex[iOrder] - arrStartIndex[iOrder] );
		}
	}

	// 5. Add the partition ( or a container if required ) in the slot dwNewPartIndex
	FillNewPartitionInfo(	llPartStartOffset, llPartSize, 
							&(pNewTable[iNewNext++]), bCreateContainer );
	if( bCreateContainer )		
	{ 
		// 5.1 Add a new 4 slots table for the new container
		pNewBuffer->PartitionCount += 4;
		PARTITION_INFORMATION* pContainerTable = &( pNewBuffer->PartitionEntry[dwNewNextIndex] );
		dwNewNextIndex += 4;
		UINT iContainerNext = 0;

		// 5.2 Create the non-container partition in the first slot 
		if( bCreateNonContainer )
			FillNewPartitionInfo(	llPartStartOffset + m_llTrackSize, llPartSize - m_llTrackSize, 
									&(pContainerTable[iContainerNext++]), FALSE );

		if( dwTableIndex == 0 )
		{
			// The new container is among primary partitions

			// 5.3 Fill with zeroes the last entries of the new container table
			for( ; iContainerNext < 4; iContainerNext++ )
			{
				memset( &(pContainerTable[iContainerNext]), 0, sizeof(PARTITION_INFORMATION) );
				pContainerTable[iContainerNext].RewritePartition = TRUE;
			}
			// All following primary partitions will be added to pNewTable after this new container
		}
		else
		{
			// The new container is inside another container

			// 5.3 Fill with zeroes the last entries of the table pNewTable
			for( ; iNewNext < 4; iNewNext++ )
			{
				memset( &(pNewTable[iNewNext]), 0, sizeof(PARTITION_INFORMATION));
				pNewTable[iNewNext].RewritePartition = TRUE;
			}

			// 5.4. The new table becomes the table of the newly created container
			// All following partitions of the parent container will be added to the new container
			pNewTable = pContainerTable;
			iNewNext = iContainerNext;

		}
	}
	
	// 6. Copy the rest of valid partitions from pTable to pNewTable ( also ordered by starting offset )
	for( ; iNext < dwPartitionCount; iNext++, iNewNext++ )
	{
		DWORD iOrder = arrOrder[iNext];
		memcpy( &(pNewTable[iNewNext]), &(pTable[iOrder]), sizeof(PARTITION_INFORMATION));
		pNewTable[iNewNext].RewritePartition = TRUE;

		if( IsContainerPartition( pNewTable[iNewNext].PartitionType ) )
		{
			memcpy( &(pNewBuffer->PartitionEntry[dwNewNextIndex]), 
					&(m_pBuffer->PartitionEntry[ arrStartIndex[iOrder] ]), 
					( arrEndIndex[iOrder] - arrStartIndex[iOrder] ) * sizeof(PARTITION_INFORMATION));
		
			if( arrStartIndex[iOrder] != dwNewNextIndex )
			{
				for( DWORD j = arrStartIndex[iOrder]; j < arrEndIndex[iOrder]; j++ )
					pNewBuffer->PartitionEntry[dwNewNextIndex++].RewritePartition = TRUE;
			}
			else
				dwNewNextIndex += ( arrEndIndex[iOrder] - arrStartIndex[iOrder] );
		}
	}

	// 7. Fill with zeroes the last entries of pNewTable
	for( ; iNewNext < 4; iNewNext++ )
	{
		memset( &(pNewTable[iNewNext]), 0, sizeof(PARTITION_INFORMATION));
		pNewTable[iNewNext].RewritePartition = TRUE;
	}

	// 8. Add the tail of m_pBuffer to the tail of pNewBuffer
	if( bCreateContainer )
		ASSERT( dwNewNextIndex == dwNextIndex + 4 );
	else
		ASSERT( dwNewNextIndex == dwNextIndex );

	memcpy( &( pNewBuffer->PartitionEntry[dwNewNextIndex] ),
			&( m_pBuffer->PartitionEntry[dwNextIndex] ),
			( m_pBuffer->PartitionCount - dwNextIndex ) * sizeof(PARTITION_INFORMATION) );
	
	if( dwNewNextIndex != dwNextIndex )
	{
		for( DWORD j = dwNextIndex; j < m_pBuffer->PartitionCount; j++ )
			pNewBuffer->PartitionEntry[dwNewNextIndex++].RewritePartition = TRUE;
	}
	else
		dwNewNextIndex += ( m_pBuffer->PartitionCount - dwNextIndex );
	ASSERT( dwNewNextIndex == pNewBuffer->PartitionCount );

	// 9. Replace the old m_pBuffer with the new buffer

	LocalFree( m_pBuffer );
	m_pBuffer = pNewBuffer;
	
	return TRUE;

	MY_CATCH_AND_THROW
}

/*
Protected method:	FillNewPartitionInfo

Purpose:			Fill a PARTITION_INFORMATION structure with the info of a new partition
					
Parameters:			[IN] LONGLONG llPartStartOffset
						The starting offset of the new partition
					[IN] LONGLONG llPartSize
						The size of the new partition
					[OUT] PARTITION_INFORMATION*
						Pointer to the structure to fill
					[IN] BOOL bContainer
						Is the new partition a container?
					

Return value:		- 
*/

void CDiskMap::FillNewPartitionInfo(	LONGLONG llPartStartOffset, LONGLONG llPartSize,/* LONGLONG llExtPartStartOffset, */
										PARTITION_INFORMATION* pPartInfo, BOOL bContainer )
{
	MY_TRY
	
	ASSERT( m_bLoaded );
	ASSERT( pPartInfo );

	pPartInfo->StartingOffset.QuadPart = llPartStartOffset;
	pPartInfo->PartitionLength.QuadPart = llPartSize; 

//#pragma message("TODO: HiddenSectors must be the number of sectors between the start of the disk or extended partition that contains the new partition and the start of the new partition?")
	pPartInfo->HiddenSectors = (DWORD)( ( llPartStartOffset ) / m_llSectorSize);

	// Don't assign any particular number for the partition. The system will take care of that
	pPartInfo->PartitionNumber = 0;

	pPartInfo->BootIndicator = FALSE;
	pPartInfo->RewritePartition = TRUE;

	if( bContainer )
	{
		pPartInfo->PartitionType = PARTITION_EXTENDED;
		pPartInfo->RecognizedPartition = FALSE;
	}
	else
	{
		//Windisk and Disk Management don't care about the number of sectors of the partition
		//They always create partitions with the type PARTITION_HUGE
		
		/*
		LONGLONG llSectorCount = ( llPartSize + m_llSectorSize - 1 ) / m_llSectorSize;
		if( llSectorCount <= CSEC_FAT )
			pPartInfo->PartitionType = PARTITION_FAT_12;
		else if( llSectorCount <= CSEC_FAT32MEG )
			pPartInfo->PartitionType = PARTITION_FAT_16;
		else
			pPartInfo->PartitionType = PARTITION_HUGE;
		*/

		pPartInfo->PartitionType = PARTITION_HUGE;
		pPartInfo->RecognizedPartition = TRUE;
	}

	MY_CATCH_AND_THROW
}

/*
Protected method:	AddError

Purpose:			Add a error message to a string .The error message will be formatted like this:
						<Disk number: >< My error message > [ System error message ]
					
Parameters:			[IN/OUT] CString& strErrors
						The string that must be appended with the error message
					[IN] UINT unErrorMsg
						Our error message. ID of a string from resources
					[IN] BOOL bAddSystemMsg
						TRUE if the latest error system message must be added to our message
					
Return value:		-
*/

void CDiskMap::AddError( 
						CString& strErrors, 
						UINT unErrorMsg, 
						BOOL bAddSystemMsg /* =FALSE */ )
{
	MY_TRY
	
	CString str, strName, strErr, strSystemErr;
	
	// Get system error message
	if( bAddSystemMsg )
	{
		LPVOID lpMsgBuf;
		if( ::FormatMessage(	
						FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,    
						NULL,
						GetLastError(),
						MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
						(LPTSTR) &lpMsgBuf,    
						0,    
						NULL ) ) // Process any inserts in lpMsgBuf.
		{			
			strSystemErr = (LPCTSTR)lpMsgBuf;
			LocalFree( lpMsgBuf );
		}
	}

	strName.Format( IDS_DISK, m_dwDiskNumber );

	// Get my error message
	strErr.LoadString(unErrorMsg);

	str.Format(_T("%s: %s %s\n"), strName, strErr, strSystemErr );
	strErrors += str;

	MY_CATCH_AND_THROW
}