mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1746 lines
46 KiB
1746 lines
46 KiB
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
FTMan
|
|
|
|
File Name:
|
|
|
|
Actions.cpp
|
|
|
|
Abstract:
|
|
|
|
Definition of global functions related to actions that can be performed in the tree and list views
|
|
Every action function receives an array of CItemData items and performs the appropriate action on them
|
|
|
|
Author:
|
|
|
|
Cristian Teodorescu December 1, 1998
|
|
|
|
Notes:
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "Resource.h"
|
|
|
|
#include "ActDlg.h"
|
|
#include "Actions.h"
|
|
#include "DiskMap.h"
|
|
#include "FrSpace.h"
|
|
#include "FTTreeVw.h"
|
|
#include "FTUtil.h"
|
|
#include "Item.h"
|
|
#include "LogVol.h"
|
|
#include "MainFrm.h"
|
|
#include "PhPart.h"
|
|
#include "RootVol.h"
|
|
|
|
#include <winbase.h>
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Assign drive letter
|
|
|
|
void ActionAssign( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
CItemData* pData;
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
pData = (CItemData*)( arrSelectedItems[0] );
|
|
ASSERT(pData);
|
|
|
|
ASSERT( ( pData->GetItemType() == IT_LogicalVolume ) ||
|
|
( pData->GetItemType() == IT_PhysicalPartition ) );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
|
|
CAssignDlg dlg( pData );
|
|
if( dlg.DoModal() != IDOK )
|
|
return;;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
TCHAR cOldDriveLetter = pData->GetDriveLetter();
|
|
BOOL bResult = FALSE, bChanged = FALSE;
|
|
|
|
// If the volume had a drive letter and now it shouldn't have any or it should have another one
|
|
// then free the old drive letter
|
|
if( cOldDriveLetter &&
|
|
( !dlg.m_bAssign || ( dlg.m_bAssign && ( cOldDriveLetter != dlg.m_cDriveLetter ) ) ) )
|
|
{
|
|
CString strMountPoint;
|
|
strMountPoint.Format(_T("%c:\\"), cOldDriveLetter);
|
|
bResult = DeleteVolumeMountPoint(strMountPoint);
|
|
if( !bResult )
|
|
{
|
|
DisplaySystemErrorMessage( IDS_ERR_DELETE_MOUNT_POINT );
|
|
AfxRefreshAll();
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
return;
|
|
}
|
|
else
|
|
bChanged = TRUE;
|
|
}
|
|
|
|
// If the volume had a drive letter and now it should have another one or if it had no drive letter and
|
|
// now it should have one then assign a the new drive letter to the volume
|
|
if( dlg.m_bAssign &&
|
|
( ( cOldDriveLetter && ( cOldDriveLetter != dlg.m_cDriveLetter ) ) || !cOldDriveLetter ) )
|
|
{
|
|
CString strMountPoint, strVolumeName;
|
|
strMountPoint.Format(_T("%c:\\"), dlg.m_cDriveLetter );
|
|
strVolumeName = pData->GetVolumeName();
|
|
strVolumeName += _T("\\");
|
|
|
|
bResult = SetVolumeMountPoint( strMountPoint, strVolumeName );
|
|
if( !bResult )
|
|
DisplaySystemErrorMessage( IDS_ERR_SET_MOUNT_POINT );
|
|
else
|
|
bChanged = TRUE;
|
|
}
|
|
|
|
if( bResult )
|
|
AfxMessageBox(IDS_MSG_ASSIGN, MB_ICONINFORMATION);
|
|
|
|
if( bChanged )
|
|
AfxRefreshAll();
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionAssign( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only 1 ) item must be selected
|
|
// 2. The selected item must be a root volume ( logical volume or physical partition )
|
|
// 3. The selected item must be valid
|
|
|
|
CItemData* pData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)( arrSelectedItems[0] );
|
|
ASSERT(pData);
|
|
|
|
if( ( pData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pData->GetItemType() != IT_PhysicalPartition ) )
|
|
goto label_disable;
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable(TRUE);
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Break mirror
|
|
|
|
/*
|
|
void ActionFtbreak( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
ASSERT( m_pParentData->GetItemType() == IT_Root );
|
|
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_LogicalVolume );
|
|
|
|
CObArray arrVolumeData;
|
|
arrVolumeData.Add(pData);
|
|
CActionDlg dlg(&arrVolumeData);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
FT_LOGICAL_DISK_ID llVolID;
|
|
pData->GetVolumeID( llVolID );
|
|
|
|
FTBreak( llVolID );
|
|
|
|
AfxRefreshAll();
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
*/
|
|
|
|
void ActionFtbreak( CObArray& arrSelectedItems)
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
CItemData* pData;
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
pData = (CItemData*)( arrSelectedItems[0] );
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_LogicalVolume );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
|
|
CLogicalVolumeData* pLogVolData = (CLogicalVolumeData*)pData;
|
|
ASSERT( pLogVolData->m_nVolType == FtMirrorSet );
|
|
|
|
// Read the members of the mirror
|
|
CObArray arrMembersData;
|
|
CString strErrors;
|
|
if( !pLogVolData->ReadMembers( arrMembersData, strErrors ) )
|
|
{
|
|
AfxMessageBox( strErrors, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
|
|
CBreakDlg dlg( pLogVolData, &arrMembersData );
|
|
if( dlg.DoModal() != IDOK )
|
|
goto label_cleanup;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
if( dlg.m_nWinnerIndex == 1 )
|
|
{
|
|
// The winner is the second member. We have to orphan the first member ( if healthy )
|
|
if( ((CItemData*)arrMembersData[0])->GetMemberStatus() == FtMemberHealthy )
|
|
{
|
|
if( !FTOrphan( pLogVolData->m_llVolID, 0 ) )
|
|
{
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
goto label_cleanup;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT( dlg.m_nWinnerIndex == 0 );
|
|
}
|
|
|
|
// Break the mirror
|
|
if( FTBreak( pLogVolData->m_llVolID ) )
|
|
{
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
// Now break all former members ( if they are FT Partitions )
|
|
// All members must be added to setListSelectedItems in order to emphasize them after
|
|
// refreshing the list view
|
|
|
|
// Select the root volumes item in the tree view
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
for( int i = 0; i < arrMembersData.GetSize(); i++ )
|
|
{
|
|
BOOL bPhPart;
|
|
|
|
CLogicalVolumeData* pMemberData = (CLogicalVolumeData*)(arrMembersData[i]);
|
|
if( pMemberData->m_nVolType == FtPartition )
|
|
bPhPart = FTBreak( pMemberData->m_llVolID );
|
|
else
|
|
bPhPart = FALSE;
|
|
|
|
if( bPhPart )
|
|
{
|
|
// The member is now a physical partition
|
|
ASSERT( pMemberData->m_nVolType == FtPartition );
|
|
idItem.m_wItemType = IT_PhysicalPartition;
|
|
idItem.m_ID.m_PhysicalPartitionID.m_ulDiskNumber = pMemberData->m_ConfigInfo.partConfig.Config.DiskNumber;
|
|
idItem.m_ID.m_PhysicalPartitionID.m_llOffset = pMemberData->m_ConfigInfo.partConfig.Config.ByteOffset;
|
|
}
|
|
else
|
|
{
|
|
// The member is still a logical volume
|
|
idItem.m_wItemType = IT_LogicalVolume;
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = pMemberData->m_llVolID;
|
|
}
|
|
setListSelectedItems.Add( idItem );
|
|
}
|
|
|
|
AfxMessageBox( IDS_MSG_FTBREAK, MB_ICONINFORMATION );
|
|
|
|
// Now refresh both views and emphasize the members of the old mirror set
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
AfxRefreshAll();
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
label_cleanup:
|
|
// Delete the array of members
|
|
for( int i = 0; i < arrMembersData.GetSize(); i++ )
|
|
{
|
|
pData = (CItemData*)(arrMembersData[i]);
|
|
if( pData )
|
|
delete pData;
|
|
}
|
|
arrMembersData.RemoveAll();
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
/*
|
|
void UpdateActionFtbreak( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// This action should be enabled only when:
|
|
// 1. single selection
|
|
// 2. Its parent is the root item
|
|
// 3. the selected item is a logical volume
|
|
|
|
POSITION pos;
|
|
|
|
if( ( arrSelectedItems.GetSize() != 1 ) ||
|
|
( m_pParentData->GetItemType() != IT_Root) )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetTreeItemType() != IT_LogicalVolume )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable(TRUE);
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
|
|
}
|
|
*/
|
|
|
|
void UpdateActionFtbreak( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only 1 ) item must be selected
|
|
// 2. The selected item must be a root volume
|
|
// 3. The selected item must be a mirror set
|
|
// 4. The selected item must be valid
|
|
|
|
CItemData* pData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_LogicalVolume )
|
|
goto label_disable;
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
if( ((CLogicalVolumeData*)pData)->m_nVolType != FtMirrorSet )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable(TRUE);
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create extended partition
|
|
|
|
void ActionCreateExtendedPartition( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
CItemData* pData;
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_FreeSpace );
|
|
|
|
CFreeSpaceData* pFreeData = (CFreeSpaceData*)pData;
|
|
|
|
ASSERT( pFreeData->m_wFreeSpaceType == FST_Primary );
|
|
|
|
if( pFreeData->m_dwExtendedPartitionCountOnLevel > 0 )
|
|
{
|
|
// There is another extended partition on the disk
|
|
AfxMessageBox( IDS_ERR_ANOTHER_EXTENDED_PARTITION, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
|
|
if( pFreeData->m_dwPartitionCountOnLevel + pFreeData->m_dwExtendedPartitionCountOnLevel >= 4 )
|
|
{
|
|
// This must not happen inside an extended partition!
|
|
AfxMessageBox( IDS_ERR_PARTITION_TABLE_FULL, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
|
|
// The starting offset of the extended partition must be the next cylinder border after the beginning of the free space
|
|
LONGLONG llExtPartStartOffset = ((LONGLONG)((pFreeData->m_llOffset + pFreeData->m_llCylinderSize - 1) / pFreeData->m_llCylinderSize)) * pFreeData->m_llCylinderSize;
|
|
CCreatePartitionDlg dlg( pFreeData, llExtPartStartOffset, TRUE );
|
|
if( dlg.DoModal() != IDOK )
|
|
return;;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
CDiskMap diskMap( pFreeData->m_dwDiskNumber );
|
|
LONGLONG llNewFreeSpaceOffset;
|
|
|
|
BOOL bResult = diskMap.CreateExtendedPartition( llExtPartStartOffset, dlg.m_llPartitionSize, llNewFreeSpaceOffset );
|
|
|
|
if( bResult )
|
|
{
|
|
AfxMessageBox( IDS_MSG_CREATE_EXTENDED_PARTITION, MB_ICONINFORMATION );
|
|
|
|
// Now refresh both views and emphasize the new partition
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootFreeSpaces;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
idItem.m_wItemType = IT_FreeSpace;
|
|
idItem.m_ID.m_FreeSpaceID.m_ulDiskNumber = pFreeData->m_dwDiskNumber;
|
|
idItem.m_ID.m_FreeSpaceID.m_llOffset = llNewFreeSpaceOffset;
|
|
setListSelectedItems.Add( idItem );
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
AfxMessageBox( IDS_ERR_CREATE_EXTENDED_PARTITION, MB_ICONSTOP );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionCreateExtendedPartition( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only 1 ) item must be selected
|
|
// 2. The selected item must be a free space
|
|
// 3. The free space must be primary ( between primary partitions )
|
|
|
|
CItemData* pData;
|
|
CFreeSpaceData* pFreeData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_FreeSpace )
|
|
goto label_disable;
|
|
|
|
pFreeData = (CFreeSpaceData*)pData;
|
|
|
|
if( pFreeData->m_wFreeSpaceType != FST_Primary )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable(TRUE);
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create partition
|
|
|
|
void ActionCreatePartition( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
CItemData* pData;
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_FreeSpace );
|
|
|
|
CFreeSpaceData* pFreeData = (CFreeSpaceData*)pData;
|
|
|
|
if( pFreeData->m_dwPartitionCountOnLevel + pFreeData->m_dwExtendedPartitionCountOnLevel >= 4 )
|
|
{
|
|
// This must not happen inside an extended partition!
|
|
ASSERT( pFreeData->m_wFreeSpaceType == FST_Primary );
|
|
AfxMessageBox( IDS_ERR_PARTITION_TABLE_FULL, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
|
|
CCreatePartitionDlg dlg( pFreeData, pFreeData->m_llOffset );
|
|
if( dlg.DoModal() != IDOK )
|
|
return;;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
CDiskMap diskMap( pFreeData->m_dwDiskNumber );
|
|
LONGLONG llExactPartStartOffset;
|
|
|
|
BOOL bResult = diskMap.CreatePartition( pFreeData->m_llOffset, dlg.m_llPartitionSize, llExactPartStartOffset );
|
|
|
|
if( bResult )
|
|
{
|
|
AfxMessageBox( IDS_MSG_CREATE_PARTITION, MB_ICONINFORMATION );
|
|
|
|
// Now refresh both views and emphasize the new partition
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
idItem.m_wItemType = IT_PhysicalPartition;
|
|
idItem.m_ID.m_PhysicalPartitionID.m_ulDiskNumber = pFreeData->m_dwDiskNumber;
|
|
idItem.m_ID.m_PhysicalPartitionID.m_llOffset = llExactPartStartOffset;
|
|
setListSelectedItems.Add( idItem );
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
AfxMessageBox( IDS_ERR_CREATE_PARTITION, MB_ICONSTOP );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionCreatePartition( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only 1 ) item must be selected
|
|
// 2. The selected item must be a free space
|
|
|
|
CItemData* pData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_FreeSpace )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable(TRUE);
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Delete
|
|
|
|
void ActionDelete( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
CItemData* pData;
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
// A special case is when the selected item is an empty extended partition
|
|
if( pData->GetItemType() == IT_FreeSpace )
|
|
{
|
|
CFreeSpaceData* pFree = (CFreeSpaceData*)pData;
|
|
ASSERT(pFree->m_wFreeSpaceType == FST_EmptyExtendedPartition );
|
|
|
|
CString strQuestion;
|
|
strQuestion.Format( IDS_QST_DELETE_EXTENDED_PARTITION, pFree->m_dwDiskNumber );
|
|
if( IDYES != AfxMessageBox(strQuestion, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
CDiskMap diskMap( pFree->m_dwDiskNumber );
|
|
if( diskMap.DeleteExtendedPartition( pFree->m_llOffset ) )
|
|
{
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootFreeSpaces;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
AfxMessageBox( IDS_MSG_DELETE_EXTENDED_PARTITION, MB_ICONINFORMATION );
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, NULL );
|
|
}
|
|
else
|
|
{
|
|
AfxMessageBox( IDS_ERR_DELETE_EXTENDED_PARTITION, MB_ICONSTOP );
|
|
AfxRefreshAll();
|
|
}
|
|
return;
|
|
}
|
|
|
|
ASSERT( ( pData->GetItemType() == IT_LogicalVolume ) ||
|
|
( pData->GetItemType() == IT_PhysicalPartition ) );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
|
|
CString strQuestion, strName;
|
|
pData->GetDisplayExtendedName(strName);
|
|
strQuestion.Format( IDS_QST_DELETE_VOLUME, strName );
|
|
if( IDYES != AfxMessageBox(strQuestion, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
// Delete the volume
|
|
BOOL bResult;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
if( pData->GetItemType() == IT_LogicalVolume )
|
|
{
|
|
FT_LOGICAL_DISK_ID llVolID;
|
|
((CLogicalVolumeData*)pData)->GetVolumeID(llVolID);
|
|
bResult = FTDelete( llVolID );
|
|
}
|
|
else if( pData->GetItemType() == IT_PhysicalPartition )
|
|
{
|
|
CDiskMap diskMap( ((CPhysicalPartitionData*)pData)->m_dwDiskNumber );
|
|
bResult = diskMap.DeletePartition( ((CPhysicalPartitionData*)pData)->m_PartInfo.StartingOffset.QuadPart );
|
|
}
|
|
else
|
|
ASSERT(FALSE);
|
|
|
|
if( bResult )
|
|
{
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
AfxMessageBox( IDS_MSG_DELETE_VOLUME, MB_ICONINFORMATION );
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, NULL );
|
|
}
|
|
else
|
|
{
|
|
if( pData->GetItemType() == IT_LogicalVolume )
|
|
AfxMessageBox(IDS_ERR_DELETE_LOGICAL_VOLUME, MB_ICONSTOP );
|
|
else if( pData->GetItemType() == IT_PhysicalPartition )
|
|
AfxMessageBox( IDS_ERR_DELETE_PHYSICAL_PARTITION, MB_ICONSTOP );
|
|
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionDelete( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only 1 ) item must be selected
|
|
// 2. The selected item must be a root volume ( logical volume or physical partition )
|
|
// 3. The selected item must be valid
|
|
//OR
|
|
// 1. 1 ( and only 1 ) item must be selected
|
|
// 2. The selected item is an empty extended partition
|
|
|
|
CItemData* pData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( ( pData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pData->GetItemType() != IT_PhysicalPartition ) )
|
|
{
|
|
if( ( pData->GetItemType() == IT_FreeSpace ) && ( ((CFreeSpaceData*)pData)->m_wFreeSpaceType == FST_EmptyExtendedPartition ) )
|
|
goto label_enable;
|
|
else
|
|
goto label_disable;
|
|
}
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
label_enable:
|
|
pCmdUI->Enable(TRUE);
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Regenerate an orphan member of a mirror set or stripe set with parity
|
|
|
|
void ActionFtinit( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_LogicalVolume );
|
|
ASSERT( pData->IsValid() );
|
|
CLogicalVolumeData* pMemberData = (CLogicalVolumeData*)pData;
|
|
|
|
CItemData* pParentData = pData->GetParentData();
|
|
ASSERT( pParentData );
|
|
ASSERT( pParentData->GetItemType() == IT_LogicalVolume );
|
|
|
|
CLogicalVolumeData* pLVParentData = (CLogicalVolumeData*)pParentData;
|
|
ASSERT( ( pLVParentData->m_nVolType == FtMirrorSet ) ||
|
|
( pLVParentData->m_nVolType == FtStripeSetWithParity ) );
|
|
|
|
ASSERT( pLVParentData->m_StateInfo.stripeState.UnhealthyMemberState == FtMemberOrphaned );
|
|
ASSERT( pLVParentData->m_StateInfo.stripeState.UnhealthyMemberNumber == ((CLogicalVolumeData*)pData)->m_unMemberIndex );
|
|
|
|
CString strMemberDisplayName, strParentDisplayName, strQuestion;
|
|
pData->GetDisplayExtendedName( strMemberDisplayName );
|
|
pLVParentData->GetDisplayExtendedName( strParentDisplayName );
|
|
strQuestion.Format( IDS_QST_FTINIT, strMemberDisplayName, strParentDisplayName );
|
|
|
|
if( IDYES != AfxMessageBox(strQuestion, MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
// Now is time to reinitialize the member
|
|
if( FTInit( pLVParentData->m_llVolID, TRUE ) )
|
|
AfxMessageBox( IDS_MSG_FTINIT, MB_ICONINFORMATION );
|
|
|
|
AfxRefreshAll();
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtinit( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only one ) item must be selected
|
|
// 2. The selected item is a logical volume
|
|
// 3. The parent of the item must be a mirror set or a stripe set with parity
|
|
// 4. The selected item must be valid
|
|
// 5. The selected item is an orphan member of its parent
|
|
|
|
CItemData* pData;
|
|
CItemData* pParentData;
|
|
CLogicalVolumeData* pLVParentData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_LogicalVolume )
|
|
goto label_disable;
|
|
|
|
if( !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
pParentData = pData->GetParentData();
|
|
if( ( pParentData == NULL ) || ( pParentData->GetItemType() != IT_LogicalVolume ) )
|
|
goto label_disable;
|
|
|
|
pLVParentData = (CLogicalVolumeData*)pParentData;
|
|
if( ( pLVParentData->m_nVolType != FtMirrorSet ) &&
|
|
( pLVParentData->m_nVolType != FtStripeSetWithParity ) )
|
|
goto label_disable;
|
|
|
|
if( pLVParentData->m_StateInfo.stripeState.UnhealthyMemberState != FtMemberOrphaned )
|
|
goto label_disable;
|
|
|
|
if( pLVParentData->m_StateInfo.stripeState.UnhealthyMemberNumber != ((CLogicalVolumeData*)pData)->m_unMemberIndex )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create mirror
|
|
|
|
void ActionFtmirror( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
int nNumVols = (int)arrSelectedItems.GetSize();
|
|
ASSERT( nNumVols == 2 );
|
|
|
|
for( int i = 0; i < nNumVols; i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
ASSERT( ( pData->GetItemType() == IT_LogicalVolume ) ||
|
|
( pData->GetItemType() == IT_PhysicalPartition ) );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
}
|
|
|
|
// Display the dialog
|
|
CActionDlg dlg(&arrSelectedItems, IDD_CREATE_MIRROR);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
// Display the warning. All data of the second volume will be lost
|
|
CString strDisplayName, strNameList, strWarning;
|
|
for( i = 1; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pVolData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pVolData);
|
|
pVolData->GetDisplayExtendedName(strDisplayName);
|
|
if( i != 1 )
|
|
strNameList += _T(", ");
|
|
strNameList += _T("\"");
|
|
strNameList += strDisplayName;
|
|
strNameList += _T("\"");
|
|
}
|
|
strWarning.Format( IDS_WRN_DATA_LOST, strNameList );
|
|
if( IDYES != AfxMessageBox(strWarning, MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
// Make sure that all selected physical partitions are converted to FT partitions
|
|
// and then get all selected items logical volume ID's
|
|
FT_LOGICAL_DISK_ID* arrVolID = (FT_LOGICAL_DISK_ID*)LocalAlloc(0, nNumVols*sizeof(FT_LOGICAL_DISK_ID) );
|
|
if( !arrVolID )
|
|
{
|
|
AfxMessageBox(IDS_ERR_ALLOCATION, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
if( !ConvertPartitionsToFT( arrSelectedItems, arrVolID ) )
|
|
{
|
|
LocalFree(arrVolID);
|
|
AfxRefreshAll();
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
return;
|
|
}
|
|
|
|
FT_LOGICAL_DISK_ID llNewSetID;
|
|
if( FTMirror( arrVolID, (WORD)nNumVols, &llNewSetID ) )
|
|
{
|
|
AfxMessageBox( IDS_MSG_FTMIRROR, MB_ICONINFORMATION );
|
|
|
|
// Refresh all and emphasize the newly created mirror
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
idItem.m_wItemType = IT_LogicalVolume;
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = llNewSetID;
|
|
setListSelectedItems.Add(idItem);
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
DeconvertPartitionsFromFT( arrSelectedItems, arrVolID );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
LocalFree(arrVolID);
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtmirror( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
// Action enabling conditions:
|
|
// 1. 2 ( and only 2 ) items must be selected
|
|
// 2. Every selected item must be a root volume ( logical volume or physical partition )
|
|
// 3. Every selected item must be valid
|
|
// 4. The disks sets intersection for every couple of selected items must be empty !
|
|
|
|
int i;
|
|
|
|
if( arrSelectedItems.GetSize() != 2 )
|
|
goto label_disable;
|
|
|
|
for( i = 0; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
if( ( pData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pData->GetItemType() != IT_PhysicalPartition ) )
|
|
goto label_disable;
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
for( int j = 0; j < i; j++ )
|
|
{
|
|
CItemData* pData2 = (CItemData*)(arrSelectedItems[j]);
|
|
CULONGSet setIntersection( pData->GetDisksSet() );
|
|
setIntersection *= pData2->GetDisksSet();
|
|
if( !setIntersection.IsEmpty() )
|
|
goto label_disable;
|
|
}
|
|
}
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
|
|
MY_CATCH
|
|
}
|
|
|
|
/*
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Orphan member
|
|
|
|
void ActionFtorphan( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_LogicalVolume );
|
|
|
|
CItemData* pParentData = pData->GetParentData();
|
|
ASSERT( pParentData );
|
|
ASSERT( pParentData->GetItemType() == IT_LogicalVolume );
|
|
ASSERT( ( ((CLogicalVolumeData*)pParentData)->m_nVolType == FtMirrorSet ) ||
|
|
( ((CLogicalVolumeData*)pParentData)->m_nVolType == FtStripeSetWithParity ) );
|
|
|
|
CObArray arrVolumeData;
|
|
arrVolumeData.Add(pData);
|
|
CActionDlg dlg(&arrSelectedItems);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
FT_LOGICAL_DISK_ID llVolID;
|
|
pLogVolData->GetVolumeID( llVolID );
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
if( FTOrphan( llVolID, iItem ) )
|
|
AfxRefreshAll();
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtorphan( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// This action should be enabled only when:
|
|
// 1. single selection
|
|
// 2. The selected item is a logical volume
|
|
// 3. the parent is a Mirror or Stripe with parity
|
|
|
|
CItemData* pData, pParentData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_LogicalVolume )
|
|
goto label_disable;
|
|
|
|
pParentData = pData->GetParentData();
|
|
if( ( pParentData == NULL ) || ( pParentData->GetItemType() != IT_LogicalVolume ) )
|
|
goto label_disable;
|
|
|
|
if( ( ((CLogicalVolumeData*)pParentData)->m_nVolType != FtMirrorSet ) &&
|
|
( ((CLogicalVolumeData*)pParentData)->m_nVolType != FtStripeSetWithParity ) )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create FT partition
|
|
|
|
void ActionFtpart( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_PhysicalPartition );
|
|
|
|
CActionDlg dlg(&arrSelectedItems);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
CPhysicalPartitionData* pPhPartData = (CPhysicalPartitionData*)pData;
|
|
ASSERT( !pPhPartData->IsFTPartition() );
|
|
ASSERT( !pData->GetVolumeName().IsEmpty() );
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
FTPart( pPhPartData->GetVolumeName(), pPhPartData->GetDriveLetter() );
|
|
|
|
AfxRefreshAll();
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtpart( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// This action should be enabled only when:
|
|
// 1. single selection
|
|
// 2. the selected item is a physical partition
|
|
// 3. This partition is not a FT partition
|
|
// 4. We have a volume name for this partition
|
|
|
|
CItemData* pData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_PhysicalPartition )
|
|
goto label_disable;
|
|
|
|
if( ((CPhysicalPartitionData*)pData)->IsFTPartition() )
|
|
goto label_disable;
|
|
|
|
if( pData->GetVolumeName().IsEmpty() )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
*/
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create stripe set
|
|
|
|
void ActionFtstripe( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
int nNumVols = (int)arrSelectedItems.GetSize();
|
|
ASSERT( nNumVols >= 2 );
|
|
|
|
for( int i = 0; i < nNumVols; i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
ASSERT( ( pData->GetItemType() == IT_LogicalVolume ) ||
|
|
( pData->GetItemType() == IT_PhysicalPartition ) );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
}
|
|
|
|
// Display the dialog
|
|
CCreateStripeDlg dlg(&arrSelectedItems);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
// Display the warning. All data of selected volumes will be lost
|
|
CString strDisplayName, strNameList, strWarning;
|
|
for( i = 0; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pVolData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pVolData);
|
|
pVolData->GetDisplayExtendedName(strDisplayName);
|
|
if( i != 0 )
|
|
strNameList += _T(", ");
|
|
strNameList += _T("\"");
|
|
strNameList += strDisplayName;
|
|
strNameList += _T("\"");
|
|
}
|
|
strWarning.Format( IDS_WRN_DATA_LOST, strNameList );
|
|
if( IDYES != AfxMessageBox(strWarning, MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
// Make sure that all selected physical partitions are converted to FT partitions
|
|
// and then get all selected items logical volume ID's
|
|
FT_LOGICAL_DISK_ID* arrVolID = (FT_LOGICAL_DISK_ID*)LocalAlloc(0, nNumVols*sizeof(FT_LOGICAL_DISK_ID) );
|
|
if( !arrVolID )
|
|
{
|
|
AfxMessageBox(IDS_ERR_ALLOCATION, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
if( !ConvertPartitionsToFT( arrSelectedItems, arrVolID ) )
|
|
{
|
|
LocalFree(arrVolID);
|
|
AfxRefreshAll();
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
return;
|
|
}
|
|
|
|
FT_LOGICAL_DISK_ID llNewSetID;
|
|
if( FTStripe( arrVolID, (WORD)nNumVols, dlg.m_ulStripeSize, &llNewSetID) )
|
|
{
|
|
AfxMessageBox( IDS_MSG_FTSTRIPE, MB_ICONINFORMATION );
|
|
|
|
// Refresh all and emphasize the newly created stripe set
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
idItem.m_wItemType = IT_LogicalVolume;
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = llNewSetID;
|
|
setListSelectedItems.Add(idItem);
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
DeconvertPartitionsFromFT( arrSelectedItems, arrVolID );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
LocalFree(arrVolID);
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtstripe( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
// Action enabling conditions:
|
|
// 1. 2 or more items must be selected
|
|
// 2. Every selected item must be a root volume ( logical volume or physical partition )
|
|
// 3. Every selected item must be valid
|
|
// 4. The disks sets intersection for every couple of selected items must be empty !
|
|
|
|
int i;
|
|
|
|
if( arrSelectedItems.GetSize() < 2 )
|
|
goto label_disable;
|
|
|
|
for( i = 0; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
if( ( pData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pData->GetItemType() != IT_PhysicalPartition ) )
|
|
goto label_disable;
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
for( int j = 0; j < i; j++ )
|
|
{
|
|
CItemData* pData2 = (CItemData*)(arrSelectedItems[j]);
|
|
CULONGSet setIntersection( pData->GetDisksSet() );
|
|
setIntersection *= pData2->GetDisksSet();
|
|
if( !setIntersection.IsEmpty() )
|
|
goto label_disable;
|
|
}
|
|
}
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
|
|
MY_CATCH
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Swap a member of a mirror set or of a stripe set with parity
|
|
|
|
void ActionFtswap( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
ASSERT( arrSelectedItems.GetSize() == 1 );
|
|
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
ASSERT( pData->GetItemType() == IT_LogicalVolume );
|
|
ASSERT( pData->IsValid() );
|
|
CLogicalVolumeData* pMemberData = (CLogicalVolumeData*)pData;
|
|
|
|
CItemData* pParentData = pData->GetParentData();
|
|
ASSERT( pParentData );
|
|
ASSERT( pParentData->GetItemType() == IT_LogicalVolume );
|
|
|
|
CLogicalVolumeData* pLVParentData = (CLogicalVolumeData*)pParentData;
|
|
ASSERT( ( pLVParentData->m_nVolType == FtMirrorSet ) ||
|
|
( pLVParentData->m_nVolType == FtStripeSetWithParity ) );
|
|
|
|
// Now get all possible replacements for the selected member
|
|
// Conditions for possible replacements:
|
|
// 1. Must be a root volume
|
|
// 2. Must be at least as big as the member size of the mirror or stripe set with parity
|
|
// 3. Must be valid
|
|
// 4. Must not be the parent of the selected member
|
|
// 5. Its disks set must not intersect with the reunion of all other members disks sets. And if its
|
|
// forefathers to the root are also members of mirrors, stripes or stripe sets with parity take
|
|
// also their siblings into consideration
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
// So, first read all logical volumes
|
|
CRootVolumesData objRoot;
|
|
CObArray arrVolumeData;
|
|
CString strErrors;
|
|
if( !objRoot.ReadMembers( arrVolumeData, strErrors ) )
|
|
{
|
|
AfxMessageBox( strErrors, MB_ICONSTOP );
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
return;
|
|
}
|
|
|
|
// Second get the member size of the parent set
|
|
LONGLONG llMemberSize;
|
|
if( pLVParentData->m_nVolType == FtMirrorSet )
|
|
llMemberSize = pLVParentData->m_ConfigInfo.mirrorConfig.MemberSize;
|
|
else if ( pLVParentData->m_nVolType == FtStripeSetWithParity )
|
|
llMemberSize = pLVParentData->m_ConfigInfo.swpConfig.MemberSize;
|
|
else
|
|
ASSERT(FALSE);
|
|
|
|
// Third get the reunion of all other siblings and forefathers' siblings disks set
|
|
CULONGSet setForbiddenDisks;
|
|
CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();
|
|
CFTTreeView* pLeftView = (CFTTreeView*)(pFrame->GetLeftPane());
|
|
ASSERT( pLeftView );
|
|
GetVolumeReplacementForbiddenDisksSet( pLeftView, pMemberData, setForbiddenDisks );
|
|
|
|
// Now select only those root volumes that match the 5 conditions enumerated above
|
|
for( int i = (int)arrVolumeData.GetSize() - 1; i >= 0; i-- )
|
|
{
|
|
CItemData* pVolData = (CItemData*)(arrVolumeData[i]);
|
|
ASSERT( pVolData );
|
|
ASSERT( pVolData->IsRootVolume() );
|
|
|
|
// Get the size of the volume
|
|
LONGLONG llVolSize;
|
|
pVolData->GetSize( llVolSize );
|
|
|
|
// Intersect the disks set of the volume with the reunion of all other members disks sets
|
|
CULONGSet setIntersection;
|
|
setIntersection = setForbiddenDisks;
|
|
setIntersection *= pVolData->GetDisksSet();
|
|
|
|
if( ( ( pVolData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pVolData->GetItemType() != IT_PhysicalPartition )
|
|
)
|
|
||
|
|
( !pVolData->IsValid() )
|
|
||
|
|
( llVolSize < llMemberSize )
|
|
||
|
|
( ( pVolData->GetItemType() == IT_LogicalVolume ) &&
|
|
( ((CLogicalVolumeData*)pVolData)->m_llVolID == pLVParentData->m_llVolID )
|
|
)
|
|
||
|
|
( !setIntersection.IsEmpty() )
|
|
)
|
|
{
|
|
delete pVolData;
|
|
arrVolumeData.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
CSwapDlg dlg(pLVParentData, pMemberData, &arrVolumeData);
|
|
CItemData* pReplData;
|
|
CString strDisplayName, strNameList, strWarning;
|
|
|
|
// If no possible replacements then error message
|
|
if( arrVolumeData.GetSize() == 0 )
|
|
{
|
|
CString str, strSize;
|
|
FormatVolumeSize( strSize, llMemberSize );
|
|
AfxFormatString1(str, IDS_ERR_NO_REPLACEMENTS, strSize );
|
|
AfxMessageBox( str, MB_ICONSTOP );
|
|
goto label_cleanup;
|
|
}
|
|
|
|
if( dlg.DoModal() != IDOK )
|
|
goto label_cleanup;
|
|
|
|
ASSERT( ( dlg.m_nReplacementIndex >= 0 ) && ( dlg.m_nReplacementIndex < arrVolumeData.GetSize() ) );
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
FT_LOGICAL_DISK_ID llReplVolID;
|
|
|
|
// If the selected replacement is a physical partition then convert it to FT Partition
|
|
pReplData = (CItemData*)(arrVolumeData[dlg.m_nReplacementIndex]);
|
|
ASSERT( pReplData );
|
|
|
|
// Display the warning. All data of the replacement volume will be lost
|
|
pReplData->GetDisplayExtendedName(strDisplayName);
|
|
strNameList += _T("\"");
|
|
strNameList += strDisplayName;
|
|
strNameList += _T("\"");
|
|
strWarning.Format( IDS_WRN_DATA_LOST, strNameList );
|
|
if( IDYES != AfxMessageBox(strWarning, MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
goto label_cleanup;
|
|
|
|
if( pReplData->GetItemType() == IT_PhysicalPartition )
|
|
{
|
|
ASSERT( !pReplData->GetVolumeName().IsEmpty() );
|
|
if( !FTPart( pReplData->GetVolumeName(),
|
|
pReplData->GetDriveLetter(),
|
|
&llReplVolID ) )
|
|
{
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
goto label_cleanup;
|
|
}
|
|
}
|
|
else if( pReplData->GetItemType() == IT_LogicalVolume )
|
|
pReplData->GetVolumeID( llReplVolID );
|
|
else
|
|
ASSERT(FALSE);
|
|
|
|
// If the selected member is not orphaned then orphan it
|
|
if( pMemberData->GetMemberStatus() != FtMemberOrphaned )
|
|
{
|
|
if( !FTOrphan( pLVParentData->m_llVolID, pMemberData->m_unMemberIndex ) )
|
|
{
|
|
if( pReplData->GetItemType() == IT_PhysicalPartition )
|
|
FTBreak( llReplVolID );
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
goto label_cleanup;
|
|
}
|
|
}
|
|
|
|
// Now is time to replace the member
|
|
FT_LOGICAL_DISK_ID llNewSetID;
|
|
if( FTRegen( pLVParentData->m_llVolID, pMemberData->m_unMemberIndex, llReplVolID, &llNewSetID ) )
|
|
{
|
|
// If the parent is a mirror set and the old member is a FT Partition break the old member
|
|
// If the parent is a stripe set with parity delete the old member
|
|
if( pLVParentData->m_nVolType == FtMirrorSet )
|
|
{
|
|
if( pMemberData->m_nVolType == FtPartition )
|
|
FTBreak( pMemberData->m_llVolID );
|
|
}
|
|
else if( pLVParentData->m_nVolType == FtStripeSetWithParity )
|
|
FTDelete( pMemberData->m_llVolID );
|
|
else
|
|
ASSERT(FALSE);
|
|
|
|
AfxMessageBox( IDS_MSG_FTREGEN, MB_ICONINFORMATION );
|
|
|
|
// Now refresh both views and emphasize the new set and its new member
|
|
CItemIDSet setAddTreeExpandedItems;
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_LogicalVolume;
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = llNewSetID;
|
|
setAddTreeExpandedItems.Add( idItem );
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
ASSERT( idItem.m_wItemType == IT_LogicalVolume );
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = llReplVolID;
|
|
setListSelectedItems.Add( idItem );
|
|
|
|
AfxRefreshAll( &setAddTreeExpandedItems, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
if( pReplData->GetItemType() == IT_PhysicalPartition )
|
|
FTBreak( llReplVolID );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
label_cleanup:
|
|
for( i = 0; i < arrVolumeData.GetSize() ; i++ )
|
|
{
|
|
CItemData* pVolData = (CItemData*)(arrVolumeData[i]);
|
|
delete pVolData;
|
|
}
|
|
arrVolumeData.RemoveAll();
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtswap( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 1 ( and only one ) item must be selected
|
|
// 2. The selected item is a logical volume
|
|
// 3. The parent of the item must be a mirror set or a stripe set with parity
|
|
// 4. The selected item must be valid
|
|
// 5. If its parent has a not healthy member, then this member should be the selected item.
|
|
// ( You cannot regenerate a member of an FT set who already has another not healthy member )
|
|
// 6. If its parent is a stripe set with parity, then the parent shouldn't be initializing
|
|
|
|
CItemData* pData;
|
|
CItemData* pParentData;
|
|
CLogicalVolumeData* pLVParentData;
|
|
|
|
if( arrSelectedItems.GetSize() != 1 )
|
|
goto label_disable;
|
|
|
|
pData = (CItemData*)(arrSelectedItems[0]);
|
|
ASSERT(pData);
|
|
|
|
if( pData->GetItemType() != IT_LogicalVolume )
|
|
goto label_disable;
|
|
|
|
if( !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
pParentData = pData->GetParentData();
|
|
if( ( pParentData == NULL ) || ( pParentData->GetItemType() != IT_LogicalVolume ) )
|
|
goto label_disable;
|
|
|
|
pLVParentData = (CLogicalVolumeData*)pParentData;
|
|
if( ( pLVParentData->m_nVolType != FtMirrorSet ) &&
|
|
( pLVParentData->m_nVolType != FtStripeSetWithParity ) )
|
|
goto label_disable;
|
|
|
|
if( ( pLVParentData->m_StateInfo.stripeState.UnhealthyMemberState != FtMemberHealthy ) &&
|
|
( pLVParentData->m_StateInfo.stripeState.UnhealthyMemberNumber != ((CLogicalVolumeData*)pData)->m_unMemberIndex ) )
|
|
goto label_disable;
|
|
|
|
if( ( pLVParentData->m_nVolType == FtStripeSetWithParity ) &&
|
|
( pLVParentData->m_StateInfo.stripeState.IsInitializing ) )
|
|
goto label_disable;
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create stripe set with parity
|
|
|
|
void ActionFtswp( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
int nNumVols = (int)arrSelectedItems.GetSize();
|
|
ASSERT( nNumVols >= 3 );
|
|
|
|
for( int i = 0; i < nNumVols; i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
ASSERT( ( pData->GetItemType() == IT_LogicalVolume ) ||
|
|
( pData->GetItemType() == IT_PhysicalPartition ) );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
}
|
|
|
|
// Display the dialog
|
|
CCreateStripeDlg dlg(&arrSelectedItems, IDD_CREATE_SWP);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
// Display the warning. All data of selected volumes will be lost
|
|
CString strDisplayName, strNameList, strWarning;
|
|
for( i = 0; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pVolData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pVolData);
|
|
pVolData->GetDisplayExtendedName(strDisplayName);
|
|
if( i != 0 )
|
|
strNameList += _T(", ");
|
|
strNameList += _T("\"");
|
|
strNameList += strDisplayName;
|
|
strNameList += _T("\"");
|
|
}
|
|
strWarning.Format( IDS_WRN_DATA_LOST, strNameList );
|
|
if( IDYES != AfxMessageBox(strWarning, MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
// Make sure that all selected physical partitions are converted to FT partitions
|
|
// and then get all selected items logical volume ID's
|
|
FT_LOGICAL_DISK_ID* arrVolID = (FT_LOGICAL_DISK_ID*)LocalAlloc(0, nNumVols*sizeof(FT_LOGICAL_DISK_ID) );
|
|
if( !arrVolID )
|
|
{
|
|
AfxMessageBox(IDS_ERR_ALLOCATION, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
if( !ConvertPartitionsToFT( arrSelectedItems, arrVolID ) )
|
|
{
|
|
LocalFree(arrVolID);
|
|
AfxRefreshAll();
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
return;
|
|
}
|
|
|
|
FT_LOGICAL_DISK_ID llNewSetID;
|
|
if( FTSWP( arrVolID, (WORD)nNumVols, dlg.m_ulStripeSize, &llNewSetID) )
|
|
{
|
|
AfxMessageBox( IDS_MSG_FTSWP, MB_ICONINFORMATION );
|
|
|
|
// Refresh all and emphasize the newly created stripe set with parity
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
idItem.m_wItemType = IT_LogicalVolume;
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = llNewSetID;
|
|
setListSelectedItems.Add(idItem);
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
DeconvertPartitionsFromFT( arrSelectedItems, arrVolID );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
LocalFree(arrVolID);
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtswp( CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
// Action enabling conditions:
|
|
// 1. 3 or more items must be selected
|
|
// 2. Every selected item must be a root volume ( logical volume or physical partition )
|
|
// 3. Every selected item must be valid
|
|
// 4. The disks sets intersection for every couple of selected items must be empty !
|
|
|
|
int i;
|
|
|
|
if( arrSelectedItems.GetSize() < 3 )
|
|
goto label_disable;
|
|
|
|
for( i = 0; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
if( ( pData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pData->GetItemType() != IT_PhysicalPartition ) )
|
|
goto label_disable;
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
|
|
for( int j = 0; j < i; j++ )
|
|
{
|
|
CItemData* pData2 = (CItemData*)(arrSelectedItems[j]);
|
|
CULONGSet setIntersection( pData->GetDisksSet() );
|
|
setIntersection *= pData2->GetDisksSet();
|
|
if( !setIntersection.IsEmpty() )
|
|
goto label_disable;
|
|
}
|
|
}
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
|
|
MY_CATCH
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Create volume set
|
|
|
|
void ActionFtvolset( CObArray& arrSelectedItems )
|
|
{
|
|
MY_TRY
|
|
|
|
CAutoRefresh ar(FALSE);
|
|
|
|
int nNumVols = (int)arrSelectedItems.GetSize();
|
|
ASSERT( nNumVols >= 2 );
|
|
|
|
// Get the array of selected items
|
|
for( int i = 0; i < nNumVols; i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
ASSERT( ( pData->GetItemType() == IT_LogicalVolume ) ||
|
|
( pData->GetItemType() == IT_PhysicalPartition ) );
|
|
ASSERT( pData->IsRootVolume() && pData->IsValid() );
|
|
}
|
|
|
|
// Display the dialog
|
|
CActionDlg dlg(&arrSelectedItems, IDD_CREATE_VOLSET);
|
|
if( dlg.DoModal() != IDOK )
|
|
return;
|
|
|
|
// Display the warning. All data of selected volumes ( except the first one ) will be lost
|
|
CString strDisplayName, strNameList, strWarning;
|
|
for( i = 1; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pVolData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pVolData);
|
|
pVolData->GetDisplayExtendedName(strDisplayName);
|
|
if( i != 1 )
|
|
strNameList += _T(", ");
|
|
strNameList += _T("\"");
|
|
strNameList += strDisplayName;
|
|
strNameList += _T("\"");
|
|
}
|
|
strWarning.Format( IDS_WRN_DATA_LOST, strNameList );
|
|
if( IDYES != AfxMessageBox(strWarning, MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2 ) )
|
|
return;
|
|
|
|
AfxGetApp()->DoWaitCursor(1);
|
|
|
|
// Make sure that all selected physical partitions are converted to FT partitions
|
|
// and then get all selected items logical volume ID's
|
|
FT_LOGICAL_DISK_ID* arrVolID = (FT_LOGICAL_DISK_ID*)LocalAlloc(0, nNumVols*sizeof(FT_LOGICAL_DISK_ID) );
|
|
if( !arrVolID )
|
|
{
|
|
AfxMessageBox(IDS_ERR_ALLOCATION, MB_ICONSTOP );
|
|
return;
|
|
}
|
|
if( !ConvertPartitionsToFT( arrSelectedItems, arrVolID ) )
|
|
{
|
|
LocalFree(arrVolID);
|
|
AfxRefreshAll();
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
return;
|
|
}
|
|
|
|
FT_LOGICAL_DISK_ID llNewSetID;
|
|
if( FTVolSet( arrVolID, (WORD)nNumVols, &llNewSetID ) )
|
|
{
|
|
AfxMessageBox( IDS_MSG_FTVOLSET, MB_ICONINFORMATION );
|
|
FTExtend( llNewSetID );
|
|
|
|
// Refresh all and emphasize the newly created volume set
|
|
CItemIDSet setTreeSelectedItems;
|
|
CItemIDSet setListSelectedItems;
|
|
CItemID idItem;
|
|
|
|
idItem.m_wItemType = IT_RootVolumes;
|
|
setTreeSelectedItems.Add( idItem );
|
|
|
|
idItem.m_wItemType = IT_LogicalVolume;
|
|
idItem.m_ID.m_LogicalVolumeID.m_llVolID = llNewSetID;
|
|
setListSelectedItems.Add(idItem);
|
|
|
|
AfxRefreshAll( NULL, &setTreeSelectedItems, &setListSelectedItems );
|
|
}
|
|
else
|
|
{
|
|
DeconvertPartitionsFromFT( arrSelectedItems, arrVolID );
|
|
AfxRefreshAll();
|
|
}
|
|
|
|
LocalFree(arrVolID);
|
|
AfxGetApp()->DoWaitCursor(-1);
|
|
|
|
MY_CATCH_AND_REPORT
|
|
}
|
|
|
|
void UpdateActionFtvolset(CCmdUI* pCmdUI, CObArray& arrSelectedItems )
|
|
{
|
|
// Action enabling conditions:
|
|
// 1. 2 or more items must be selected
|
|
// 2. Every selected item must be a root volume ( logical volume or physical partition )
|
|
// 3. Every selected item must be valid
|
|
|
|
int i;
|
|
|
|
if( arrSelectedItems.GetSize() < 2 )
|
|
goto label_disable;
|
|
|
|
for( i = 0; i < arrSelectedItems.GetSize(); i++ )
|
|
{
|
|
CItemData* pData = (CItemData*)(arrSelectedItems[i]);
|
|
ASSERT(pData);
|
|
if( ( pData->GetItemType() != IT_LogicalVolume ) &&
|
|
( pData->GetItemType() != IT_PhysicalPartition ) )
|
|
goto label_disable;
|
|
if( !pData->IsRootVolume() || !pData->IsValid() )
|
|
goto label_disable;
|
|
}
|
|
|
|
pCmdUI->Enable( TRUE );
|
|
return;
|
|
|
|
label_disable:
|
|
pCmdUI->Enable(FALSE);
|
|
}
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|