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.
1267 lines
36 KiB
1267 lines
36 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
asrclus.c
|
|
|
|
Abstract:
|
|
|
|
This module contains ASR routines specifically
|
|
implemented for clusters.
|
|
|
|
Notes:
|
|
|
|
Naming conventions:
|
|
_AsrpXXX private ASR Macros
|
|
AsrpXXX private ASR routines
|
|
AsrXXX Publically defined and documented routines
|
|
|
|
Author:
|
|
|
|
Guhan Suriyanarayanan (guhans) 27-May-2000
|
|
|
|
Environment:
|
|
|
|
User-mode only.
|
|
|
|
Revision History:
|
|
|
|
27-May-2000 guhans
|
|
Moved cluster-related routines from asr.c to asrclus.c
|
|
|
|
01-Mar-2000 guhans
|
|
Initial implementation for cluster-specific routines
|
|
in asr.c
|
|
|
|
--*/
|
|
#include "setupp.h"
|
|
#pragma hdrstop
|
|
|
|
#include <mountmgr.h> // mountmgr ioctls
|
|
#include <clusstor.h> // Cluster API's
|
|
#include <resapi.h> // Cluster ResUtilEnumResources
|
|
#include <clusdisk.h>
|
|
|
|
#define THIS_MODULE 'C'
|
|
#include "asrpriv.h" // Private ASR definitions and routines
|
|
|
|
//
|
|
// --------
|
|
// typedef's local to this module
|
|
// --------
|
|
//
|
|
|
|
//
|
|
// The cluster resource related typdefs
|
|
//
|
|
typedef DWORD (* PFN_CLUSTER_RESOURCE_CONTROL) (
|
|
IN HRESOURCE hResource,
|
|
IN OPTIONAL HNODE hHostNode,
|
|
IN DWORD dwControlCode,
|
|
IN LPVOID lpInBuffer,
|
|
IN DWORD cbInBufferSize,
|
|
OUT LPVOID lpOutBuffer,
|
|
IN DWORD cbOutBufferSize,
|
|
OUT LPDWORD lpcbBytesReturned
|
|
);
|
|
|
|
typedef DWORD (* PFN_RES_UTIL_ENUM_RESOURCES) (
|
|
IN HRESOURCE hSelf,
|
|
IN LPCWSTR lpszResTypeName,
|
|
IN LPRESOURCE_CALLBACK pResCallBack,
|
|
IN PVOID pParameter
|
|
);
|
|
|
|
|
|
//
|
|
// ---------
|
|
// global variables used within this module.
|
|
// --------
|
|
//
|
|
PFN_CLUSTER_RESOURCE_CONTROL pfnClusterResourceControl;
|
|
|
|
|
|
//
|
|
// ---------
|
|
// constants used within this module.
|
|
// --------
|
|
//
|
|
const WCHAR ASR_CLUSTER_PHYSICAL_DISK[] = L"Physical Disk";
|
|
const WCHAR ASR_CLUSTER_CLUSAPI_DLL_NAME[] = L"clusapi.dll";
|
|
const WCHAR ASR_CLUSTER_RESUTILS_DLL_NAME[] = L"resutils.dll";
|
|
|
|
//
|
|
// The following must be single-byte ansi chars
|
|
//
|
|
const CHAR ASR_CLUSTER_DLL_MODULE_NAME[] = "%SystemRoot%\\system32\\syssetup.dll";
|
|
const CHAR ASR_CLUSTER_DLL_PROC_NAME[] = "AsrpGetLocalDiskInfo";
|
|
const CHAR ASR_CLUSTER_CLUSAPI_PROC_NAME[] = "ClusterResourceControl";
|
|
const CHAR ASR_CLUSTER_RESUTILS_PROC_NAME[] = "ResUtilEnumResources";
|
|
|
|
|
|
//
|
|
// --------
|
|
// function implementations
|
|
// --------
|
|
//
|
|
|
|
|
|
//
|
|
// --- AsrpGetLocalVolumeInfo and related helper functions
|
|
//
|
|
|
|
//
|
|
// The disk info struct we get back from the remote nodes on the cluster will have
|
|
// offsets instead of pointers--we can convert this back to pointers by just adding
|
|
// back the base address. We also mark that this struct is packed--so we should just
|
|
// free the entire struct instead of freeing each pointer in the struct.
|
|
//
|
|
BOOL
|
|
AsrpUnPackDiskInfo(
|
|
IN PVOID InBuffer,
|
|
IN CONST DWORD dwSizeBuffer
|
|
)
|
|
{
|
|
|
|
PASR_DISK_INFO pBuffer = (PASR_DISK_INFO) InBuffer;
|
|
DWORD dwNextOffset = 0;
|
|
|
|
/* if (!((pBuffer->pDriveLayoutEx) && (pBuffer->pDiskGeometry) && (pBuffer->pPartition0Ex))) {
|
|
return FALSE;
|
|
}
|
|
*/
|
|
|
|
//
|
|
// Do some sanity checks to ensure that the offsets in the structure make sense
|
|
//
|
|
if (pBuffer->pDriveLayoutEx) {
|
|
//
|
|
// Make sure the buffer is big enough to hold this struct, and
|
|
// that the entire struct fits within the buffer
|
|
//
|
|
if ((dwSizeBuffer < sizeof(DRIVE_LAYOUT_INFORMATION_EX)) ||
|
|
(PtrToUlong(pBuffer->pDriveLayoutEx) > (dwSizeBuffer - sizeof(DRIVE_LAYOUT_INFORMATION_EX)))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the minimum value for the next struct
|
|
//
|
|
dwNextOffset = PtrToUlong(pBuffer->pDriveLayoutEx) + sizeof(DRIVE_LAYOUT_INFORMATION_EX);
|
|
}
|
|
|
|
if (pBuffer->pDiskGeometry) {
|
|
//
|
|
// Make sure this struct doesn't overlap with the previous struct
|
|
//
|
|
if (PtrToUlong(pBuffer->pDiskGeometry) < dwNextOffset) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure we haven't run off the end
|
|
//
|
|
if (dwNextOffset > dwSizeBuffer) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure the rest of buffer is big enough to hold this struct, and
|
|
// that the entire struct fits within the buffer
|
|
//
|
|
if (((dwSizeBuffer - dwNextOffset) < sizeof(DISK_GEOMETRY)) ||
|
|
(PtrToUlong(pBuffer->pDiskGeometry) > (dwSizeBuffer - sizeof(DISK_GEOMETRY)))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the minimum value for the next struct
|
|
//
|
|
dwNextOffset = PtrToUlong(pBuffer->pDiskGeometry) + sizeof(DISK_GEOMETRY);
|
|
}
|
|
|
|
if (pBuffer->pPartition0Ex) {
|
|
//
|
|
// Make sure this struct doesn't overlap with the previous struct
|
|
//
|
|
if (PtrToUlong(pBuffer->pPartition0Ex) < dwNextOffset) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure we haven't run off the end
|
|
//
|
|
if (dwNextOffset > dwSizeBuffer) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure the rest of buffer is big enough to hold this struct, and
|
|
// that the entire struct fits within the buffer
|
|
//
|
|
if (((dwSizeBuffer - dwNextOffset) < sizeof(PARTITION_INFORMATION_EX)) ||
|
|
(PtrToUlong(pBuffer->pPartition0Ex) > (dwSizeBuffer - sizeof(PARTITION_INFORMATION_EX)))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the minimum value for the next struct
|
|
//
|
|
dwNextOffset = PtrToUlong(pBuffer->pPartition0Ex) + sizeof(PARTITION_INFORMATION_EX);
|
|
}
|
|
|
|
if (pBuffer->PartitionInfoTable) {
|
|
//
|
|
// Make sure this struct doesn't overlap with the previous struct
|
|
//
|
|
if (PtrToUlong(pBuffer->PartitionInfoTable) < dwNextOffset) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure we haven't run off the end
|
|
//
|
|
if (dwNextOffset > dwSizeBuffer) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure the rest of buffer is big enough to hold this struct, and
|
|
// that the entire struct fits within the buffer
|
|
//
|
|
if (((dwSizeBuffer - dwNextOffset) < sizeof(ASR_PTN_INFO)) ||
|
|
(PtrToUlong(pBuffer->PartitionInfoTable) > (dwSizeBuffer - sizeof(ASR_PTN_INFO)))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the minimum value for the next struct
|
|
//
|
|
dwNextOffset = PtrToUlong(pBuffer->PartitionInfoTable) + sizeof(ASR_PTN_INFO);
|
|
}
|
|
|
|
if (pBuffer->pScsiAddress) {
|
|
//
|
|
// Make sure this struct doesn't overlap with the previous struct
|
|
//
|
|
if (PtrToUlong(pBuffer->pScsiAddress) < dwNextOffset) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure we haven't run off the end
|
|
//
|
|
if (dwNextOffset > dwSizeBuffer) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure the rest of buffer is big enough to hold this struct, and
|
|
// that the entire struct fits within the buffer
|
|
//
|
|
if (((dwSizeBuffer - dwNextOffset) < sizeof(SCSI_ADDRESS)) ||
|
|
(PtrToUlong(pBuffer->pScsiAddress) > (dwSizeBuffer - sizeof(SCSI_ADDRESS)))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the minimum value for the next struct
|
|
//
|
|
dwNextOffset = PtrToUlong(pBuffer->pScsiAddress) + sizeof(SCSI_ADDRESS);
|
|
}
|
|
|
|
//
|
|
// Make sure we haven't run off the end
|
|
//
|
|
if (dwNextOffset > dwSizeBuffer) {
|
|
return FALSE;
|
|
}
|
|
|
|
pBuffer->IsPacked = TRUE;
|
|
|
|
//
|
|
// Convert the offsets to pointers
|
|
//
|
|
if (pBuffer->pDriveLayoutEx) {
|
|
pBuffer->pDriveLayoutEx = (PDRIVE_LAYOUT_INFORMATION_EX) ((LPBYTE)pBuffer + PtrToUlong(pBuffer->pDriveLayoutEx));
|
|
}
|
|
|
|
if (pBuffer->pDiskGeometry) {
|
|
pBuffer->pDiskGeometry = (PDISK_GEOMETRY) ((LPBYTE)pBuffer + PtrToUlong((LPBYTE)pBuffer->pDiskGeometry));
|
|
}
|
|
|
|
if (pBuffer->pPartition0Ex) {
|
|
pBuffer->pPartition0Ex = (PPARTITION_INFORMATION_EX) ((LPBYTE)pBuffer + PtrToUlong(pBuffer->pPartition0Ex));
|
|
}
|
|
|
|
if (pBuffer->PartitionInfoTable) {
|
|
pBuffer->PartitionInfoTable = (PASR_PTN_INFO) ((LPBYTE)pBuffer + PtrToUlong(pBuffer->PartitionInfoTable));
|
|
}
|
|
|
|
if (pBuffer->pScsiAddress) {
|
|
pBuffer->pScsiAddress = (PSCSI_ADDRESS) ((LPBYTE)pBuffer + PtrToUlong(pBuffer->pScsiAddress));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Copies the info in pLocalDisk to a flat buffer pointed to by lpOutBuffer.
|
|
// The pointers are changed to offsets from the start of the buffer.
|
|
//
|
|
DWORD
|
|
AsrpPackDiskInfo(
|
|
IN PASR_DISK_INFO pLocalDisk,
|
|
OUT PVOID lpOutBuffer,
|
|
IN DWORD nOutBufferSize,
|
|
OUT LPDWORD lpBytesReturned
|
|
)
|
|
{
|
|
|
|
DWORD reqdSize = 0;
|
|
PASR_DISK_INFO pBuffer = NULL;
|
|
DWORD offset = 0;
|
|
|
|
MYASSERT(pLocalDisk);
|
|
|
|
//
|
|
// Calculate required size
|
|
//
|
|
reqdSize = sizeof (ASR_DISK_INFO) +
|
|
pLocalDisk->sizeDriveLayoutEx +
|
|
pLocalDisk->sizeDiskGeometry +
|
|
pLocalDisk->sizePartition0Ex +
|
|
pLocalDisk->sizePartitionInfoTable;
|
|
|
|
if (pLocalDisk->pScsiAddress) {
|
|
reqdSize += sizeof(SCSI_ADDRESS);
|
|
}
|
|
|
|
if (lpBytesReturned) {
|
|
*lpBytesReturned = reqdSize;
|
|
}
|
|
|
|
if (reqdSize > nOutBufferSize) {
|
|
return ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
//
|
|
// Copy the ASR_DISK_INFO struct over to outBuffer
|
|
//
|
|
memcpy(lpOutBuffer, pLocalDisk, sizeof(ASR_DISK_INFO));
|
|
pBuffer = (PASR_DISK_INFO) lpOutBuffer;
|
|
offset = sizeof(ASR_DISK_INFO); // offset where the next struct will be copied
|
|
|
|
//
|
|
// Now, we go through the buffer and convert all pointers to offsets,
|
|
// and copy over the structs they were pointing to.
|
|
//
|
|
|
|
//
|
|
// First pointer: PWSTR DevicePath;
|
|
// Since the DevicePath makes sense only in the context of the local node,
|
|
// we return NULL to the remote node.
|
|
//
|
|
pBuffer->DevicePath = NULL;
|
|
|
|
//
|
|
// Next pointer: PDRIVE_LAYOUT_INFORMATION_EX pDriveLayoutEx;
|
|
//
|
|
if (pLocalDisk->pDriveLayoutEx) {
|
|
memcpy(((LPBYTE)lpOutBuffer + offset),
|
|
pLocalDisk->pDriveLayoutEx,
|
|
pLocalDisk->sizeDriveLayoutEx
|
|
);
|
|
|
|
pBuffer->pDriveLayoutEx = (PDRIVE_LAYOUT_INFORMATION_EX) UlongToPtr(offset);
|
|
offset += pLocalDisk->sizeDriveLayoutEx;
|
|
}
|
|
|
|
//
|
|
// Next pointer: PDISK_GEOMETRY pDiskGeometry;
|
|
//
|
|
if (pLocalDisk->pDiskGeometry) {
|
|
memcpy(((LPBYTE)lpOutBuffer + offset),
|
|
pLocalDisk->pDiskGeometry,
|
|
pLocalDisk->sizeDiskGeometry
|
|
);
|
|
|
|
pBuffer->pDiskGeometry = (PDISK_GEOMETRY) UlongToPtr(offset);
|
|
offset += pLocalDisk->sizeDiskGeometry;
|
|
}
|
|
|
|
//
|
|
// Next pointer: PPARTITION_INFORMATION_EX pPartition0Ex;
|
|
//
|
|
if (pLocalDisk->pPartition0Ex) {
|
|
memcpy(((LPBYTE)lpOutBuffer + offset),
|
|
pLocalDisk->pPartition0Ex,
|
|
pLocalDisk->sizePartition0Ex
|
|
);
|
|
|
|
pBuffer->pPartition0Ex= (PPARTITION_INFORMATION_EX) UlongToPtr(offset);
|
|
offset += pLocalDisk->sizePartition0Ex;
|
|
}
|
|
|
|
//
|
|
// Next pointer: PASR_PTN_INFO PartitionInfoTable;
|
|
//
|
|
if (pLocalDisk->PartitionInfoTable) {
|
|
memcpy(((LPBYTE)lpOutBuffer + offset),
|
|
pLocalDisk->PartitionInfoTable,
|
|
pLocalDisk->sizePartitionInfoTable
|
|
);
|
|
|
|
pBuffer->PartitionInfoTable = (PASR_PTN_INFO) UlongToPtr(offset);
|
|
offset += pLocalDisk->sizePartitionInfoTable;
|
|
}
|
|
|
|
//
|
|
// Last pointer: PSCSI_ADDRESS pScsiAddress;
|
|
//
|
|
if (pLocalDisk->pScsiAddress) {
|
|
memcpy(((LPBYTE)lpOutBuffer + offset),
|
|
pLocalDisk->pScsiAddress,
|
|
sizeof(SCSI_ADDRESS)
|
|
);
|
|
|
|
pBuffer->pScsiAddress = (PSCSI_ADDRESS) UlongToPtr(offset);
|
|
offset += sizeof(SCSI_ADDRESS);
|
|
}
|
|
|
|
MYASSERT(offset <= nOutBufferSize);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
AsrpGetLocalDiskInfo(
|
|
IN LPSTR lpszDeviceName,
|
|
IN LPSTR lpszContextString, // not used
|
|
OUT PVOID lpOutBuffer,
|
|
IN DWORD nOutBufferSize,
|
|
OUT LPDWORD lpBytesReturned
|
|
)
|
|
{
|
|
PASR_DISK_INFO pLocalDisk = NULL;
|
|
HANDLE heapHandle = NULL;
|
|
DWORD status = ERROR_SUCCESS;
|
|
BOOL result = FALSE;
|
|
ULONG MaxDeviceNumber = 0;
|
|
DWORD cchReqdSize = 0;
|
|
|
|
heapHandle = GetProcessHeap();
|
|
|
|
//
|
|
// Either the BytesReturned must be non-null (he's getting the required size),
|
|
// or the lpOutBuffer must be non-null (he's getting the data).
|
|
//
|
|
_AsrpErrExitCode(!(lpOutBuffer || lpBytesReturned), status, ERROR_INVALID_PARAMETER);
|
|
if (lpBytesReturned) {
|
|
*lpBytesReturned = 0;
|
|
}
|
|
|
|
pLocalDisk = (PASR_DISK_INFO) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof (ASR_DISK_INFO)
|
|
);
|
|
_AsrpErrExitCode(!pLocalDisk, status, ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
cchReqdSize = MultiByteToWideChar(CP_ACP,
|
|
0,
|
|
lpszDeviceName,
|
|
-1,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
pLocalDisk->DevicePath = (PWSTR) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
(cchReqdSize + 1) * (sizeof(WCHAR))
|
|
);
|
|
_AsrpErrExitCode(!(pLocalDisk->DevicePath), status, ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
result = MultiByteToWideChar(CP_ACP,
|
|
0,
|
|
lpszDeviceName,
|
|
-1,
|
|
pLocalDisk->DevicePath,
|
|
(cchReqdSize + 1)
|
|
);
|
|
_AsrpErrExitCode(!result, status, ERROR_INVALID_PARAMETER);
|
|
|
|
//
|
|
// Get the disk layout information
|
|
//
|
|
result = AsrpInitLayoutInformation(NULL, pLocalDisk, &MaxDeviceNumber, TRUE, TRUE);
|
|
_AsrpErrExitCode(!result, status, GetLastError());
|
|
// _AsrpErrExitCode(result && GetLastErr what if createfile fails?
|
|
|
|
result = AsrpFreeNonFixedMedia(&pLocalDisk);
|
|
_AsrpErrExitCode(!result, status, GetLastError());
|
|
_AsrpErrExitCode(!pLocalDisk, status, ERROR_SUCCESS);
|
|
|
|
//
|
|
// Copy it to the out buffer without any pointers
|
|
//
|
|
status = AsrpPackDiskInfo(pLocalDisk, lpOutBuffer, nOutBufferSize, lpBytesReturned);
|
|
|
|
|
|
EXIT:
|
|
AsrpFreeStateInformation(&pLocalDisk, NULL);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
AsrpIsOfflineClusteredDisk(
|
|
IN CONST HANDLE hDisk
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Utility to check if the current disk is a shared cluster disk that's owned
|
|
by a different node (and is hence inaccessible).
|
|
|
|
Based on code-snippet graciously donated by SteveDz.
|
|
|
|
Arguments:
|
|
|
|
hDisk - Supplies a handle (no access required) to the disk of interest.
|
|
|
|
Return Value:
|
|
|
|
If the function succeeds and the disk is a shared cluster disk that is
|
|
offline on the current node, the return value is a nonzero value.
|
|
|
|
If the function fails, or if the disk is not a shared cluster disk that
|
|
is offline on the current node (ie, is a local unshared disk, or
|
|
a shared disk that online to the current node) the return value
|
|
is zero.
|
|
|
|
--*/
|
|
{
|
|
BOOL result = FALSE;
|
|
DWORD bytesReturned = 0;
|
|
DiskState diskState = DiskOffline;
|
|
|
|
if ((!hDisk) || (INVALID_HANDLE_VALUE == hDisk)) {
|
|
//
|
|
// We couldn't open the disk--let's assume that it's not an offline
|
|
// clustered disk.
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// To get current disk state, don't specify input buffer.
|
|
// Current disk state returned in output buffer.
|
|
//
|
|
result = DeviceIoControl(hDisk,
|
|
IOCTL_DISK_CLUSTER_GET_STATE,
|
|
NULL,
|
|
0,
|
|
&diskState,
|
|
sizeof(DiskState),
|
|
&bytesReturned,
|
|
FALSE
|
|
);
|
|
|
|
if ((result) && (DiskOffline == diskState)) {
|
|
return TRUE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// ---- AsrpInitClusterSharedDisks and related helper functions
|
|
//
|
|
|
|
BOOL
|
|
AsrpIsClusteredDiskSame(
|
|
IN PASR_DISK_INFO currentDisk,
|
|
IN PASR_DISK_INFO clusterDisk
|
|
)
|
|
{
|
|
|
|
if (!clusterDisk || !currentDisk) {
|
|
MYASSERT(0 && L"Invalid parameter, Disk is NULL");
|
|
return FALSE;
|
|
}
|
|
|
|
if (currentDisk->Style != clusterDisk->Style) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (PARTITION_STYLE_MBR == clusterDisk->Style) { // currently always true
|
|
if (clusterDisk->pDriveLayoutEx) {
|
|
if (currentDisk->pDriveLayoutEx) {
|
|
return (currentDisk->pDriveLayoutEx->Mbr.Signature == clusterDisk->pDriveLayoutEx->Mbr.Signature);
|
|
}
|
|
else {
|
|
return (currentDisk->TempSignature == clusterDisk->pDriveLayoutEx->Mbr.Signature);
|
|
}
|
|
}
|
|
else {
|
|
MYASSERT(0 && L"Cluster disk drive layout is NULL");
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
else {
|
|
if (clusterDisk->pDriveLayoutEx && currentDisk->pDriveLayoutEx) {
|
|
return (IsEqualGUID(&(currentDisk->pDriveLayoutEx->Gpt.DiskId), &(clusterDisk->pDriveLayoutEx->Gpt.DiskId)));
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
DWORD
|
|
AsrpResourceCallBack(
|
|
RESOURCE_HANDLE hOriginal,
|
|
RESOURCE_HANDLE hResource,
|
|
PVOID lpParams
|
|
)
|
|
{
|
|
DISK_DLL_EXTENSION_INFO inBuffer;
|
|
|
|
PBYTE outBuffer = NULL;
|
|
|
|
DWORD sizeOutBuffer = 0,
|
|
bytesReturned = 0;
|
|
|
|
DWORD status = ERROR_SUCCESS;
|
|
|
|
PASR_DISK_INFO currentDisk = (PASR_DISK_INFO) lpParams,
|
|
clusterDisk = NULL,
|
|
prevDisk = NULL;
|
|
|
|
BOOL bResult = FALSE;
|
|
|
|
HANDLE heapHandle = NULL;
|
|
BOOL done = FALSE;
|
|
|
|
if (!lpParams) {
|
|
//
|
|
// The system must have at least one disk that has been enumerated
|
|
// already (the system disk, at least!), so our disk list shouldn't be NULL.
|
|
//
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
heapHandle = GetProcessHeap();
|
|
MYASSERT(heapHandle);
|
|
|
|
//
|
|
// Allocate a reasonably-sized memory for the out buffer. If this isn't
|
|
// big enough, we'll re-allocate.
|
|
//
|
|
sizeOutBuffer = ASR_BUFFER_SIZE;
|
|
outBuffer = (PBYTE) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeOutBuffer
|
|
);
|
|
_AsrpErrExitCode(!outBuffer, status, ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
//
|
|
// Call AsrpGetLocalDiskInfo on the node owning this disk resource
|
|
//
|
|
ZeroMemory(&inBuffer, sizeof(inBuffer));
|
|
inBuffer.MajorVersion = NT5_MAJOR_VERSION;
|
|
strcpy(inBuffer.DllModuleName, ASR_CLUSTER_DLL_MODULE_NAME);
|
|
strcpy(inBuffer.DllProcName, ASR_CLUSTER_DLL_PROC_NAME);
|
|
|
|
status = (pfnClusterResourceControl) (hResource,
|
|
NULL,
|
|
CLUSCTL_RESOURCE_STORAGE_DLL_EXTENSION,
|
|
&inBuffer,
|
|
sizeof(DISK_DLL_EXTENSION_INFO),
|
|
(PVOID) outBuffer,
|
|
sizeOutBuffer,
|
|
&bytesReturned
|
|
);
|
|
|
|
if (ERROR_INSUFFICIENT_BUFFER == status) {
|
|
//
|
|
// The buffer wasn't big enough, re-allocate as needed
|
|
//
|
|
_AsrpHeapFree(outBuffer);
|
|
|
|
sizeOutBuffer = bytesReturned;
|
|
outBuffer = (PBYTE) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeOutBuffer
|
|
);
|
|
_AsrpErrExitCode(!outBuffer, status, ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
status = (pfnClusterResourceControl) (
|
|
hResource,
|
|
NULL,
|
|
CLUSCTL_RESOURCE_STORAGE_DLL_EXTENSION,
|
|
&inBuffer,
|
|
sizeof(DISK_DLL_EXTENSION_INFO),
|
|
(PVOID) outBuffer,
|
|
sizeOutBuffer,
|
|
&bytesReturned
|
|
);
|
|
}
|
|
_AsrpErrExitCode((ERROR_SUCCESS != status), status, status);
|
|
|
|
//
|
|
// outBuffer has a packed disk info struct (ie the pointers are offsets).
|
|
//
|
|
bResult = AsrpUnPackDiskInfo(outBuffer, sizeOutBuffer);
|
|
_AsrpErrExitCode(!bResult, status, ERROR_INVALID_DATA);
|
|
|
|
clusterDisk = (PASR_DISK_INFO) outBuffer;
|
|
clusterDisk->IsClusterShared = TRUE;
|
|
clusterDisk->IsPacked = TRUE; // so that we free this properly
|
|
|
|
//
|
|
// Check if clusterDisk already has info in our list (ie is owned
|
|
// locally)
|
|
//
|
|
// Note that for now, clusterDisk is always MBR (since clusters don't
|
|
// support shared GPT disks). We don't care here, we handle GPT as well.
|
|
//
|
|
done = FALSE;
|
|
prevDisk = NULL;
|
|
while (currentDisk && !done) {
|
|
|
|
if (AsrpIsClusteredDiskSame(currentDisk, clusterDisk)) {
|
|
|
|
if (currentDisk->pDriveLayoutEx) {
|
|
//
|
|
// This disk is owned by the local node (correct?), since
|
|
// we would not have gotten the pDriveLayout otherwise
|
|
//
|
|
currentDisk->IsClusterShared = TRUE;
|
|
currentDisk->IsPacked = FALSE;
|
|
|
|
//
|
|
// We don't need the info returned by clusterDisk, we have
|
|
// it in currentDisk already.
|
|
//
|
|
_AsrpHeapFree(clusterDisk); // it's packed.
|
|
|
|
}
|
|
else {
|
|
//
|
|
// This disk is owned by a remote node. So we add clusterDisk
|
|
// in to our list now. We'll remove currentDisk from our
|
|
// list later (in RemoveNonFixedDevices).
|
|
//
|
|
// First though, we copy over DevicePath and DeviceNumber
|
|
// from currentDisk, since these are relative to the local
|
|
// machine
|
|
//
|
|
if (currentDisk->DevicePath) {
|
|
|
|
clusterDisk->DevicePath = (PWSTR) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(WCHAR) * (wcslen(currentDisk->DevicePath) + 1)
|
|
);
|
|
|
|
wcscpy(clusterDisk->DevicePath, currentDisk->DevicePath);
|
|
}
|
|
|
|
clusterDisk->DeviceNumber = currentDisk->DeviceNumber;
|
|
//
|
|
// Don't bother freeing currentDisk, it'll get taken care
|
|
// of in RemoveNonFixedDevices.
|
|
//
|
|
clusterDisk->pNext = currentDisk->pNext;
|
|
currentDisk->pNext = clusterDisk;
|
|
|
|
currentDisk = clusterDisk; // move forward by one (don't really need to since done will be set to TRUE and we'll get out of the loop)
|
|
}
|
|
|
|
done = TRUE;
|
|
}
|
|
|
|
prevDisk = currentDisk;
|
|
currentDisk = currentDisk->pNext;
|
|
}
|
|
|
|
|
|
if (!done) {
|
|
//
|
|
// This disk was not found in our list (strange), let's add
|
|
// it in at the end
|
|
//
|
|
// MYASSERT(0 && L"Clustered disk not found in OriginalDiskList, adding it to the end");
|
|
clusterDisk->pNext = NULL;
|
|
prevDisk->pNext = clusterDisk;
|
|
}
|
|
|
|
|
|
EXIT:
|
|
//
|
|
// Free up outBuffer on failure. On success, outBuffer shouldn't
|
|
// be freed, it will either be part of OriginalDiskList or already
|
|
// be freed.
|
|
//
|
|
if (ERROR_SUCCESS != status) {
|
|
_AsrpHeapFree(outBuffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
BOOL
|
|
AsrpInitClusterSharedDisks(
|
|
IN PASR_DISK_INFO OriginalDiskList
|
|
)
|
|
{
|
|
DWORD status = ERROR_SUCCESS,
|
|
dwOldError;
|
|
|
|
HMODULE hClusApi = NULL,
|
|
hResUtils = NULL;
|
|
|
|
PFN_RES_UTIL_ENUM_RESOURCES pfnResUtilEnumResources = NULL;
|
|
|
|
dwOldError = GetLastError();
|
|
|
|
if (!OriginalDiskList) {
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
hClusApi = LoadLibraryW(ASR_CLUSTER_CLUSAPI_DLL_NAME);
|
|
_AsrpErrExitCode(!hClusApi, status, GetLastError());
|
|
|
|
pfnClusterResourceControl = (PFN_CLUSTER_RESOURCE_CONTROL) GetProcAddress(
|
|
hClusApi,
|
|
ASR_CLUSTER_CLUSAPI_PROC_NAME
|
|
);
|
|
_AsrpErrExitCode(!pfnClusterResourceControl, status, GetLastError());
|
|
|
|
hResUtils = LoadLibraryW(ASR_CLUSTER_RESUTILS_DLL_NAME);
|
|
_AsrpErrExitCode(!hResUtils, status, GetLastError());
|
|
|
|
pfnResUtilEnumResources = (PFN_RES_UTIL_ENUM_RESOURCES) GetProcAddress(
|
|
hResUtils,
|
|
ASR_CLUSTER_RESUTILS_PROC_NAME
|
|
);
|
|
_AsrpErrExitCode(!pfnResUtilEnumResources, status, GetLastError());
|
|
|
|
status = (pfnResUtilEnumResources) (NULL,
|
|
ASR_CLUSTER_PHYSICAL_DISK,
|
|
AsrpResourceCallBack,
|
|
OriginalDiskList
|
|
);
|
|
|
|
EXIT:
|
|
if (hClusApi) {
|
|
FreeLibrary(hClusApi);
|
|
}
|
|
|
|
if (hResUtils) {
|
|
FreeLibrary(hResUtils);
|
|
}
|
|
|
|
// ResUtil will fail if we aren't on a cluster, but that's fine.
|
|
SetLastError(dwOldError);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// --- AsrpGetLocalVolumeInfo and related helper functions
|
|
//
|
|
|
|
//
|
|
// The following two definitions are from asr_fmt:dr_state.cpp. This MUST be
|
|
// kept in sync.
|
|
//
|
|
typedef struct _ASRFMT_CLUSTER_VOLUME_INFO {
|
|
|
|
UINT driveType;
|
|
|
|
DWORD PartitionNumber;
|
|
|
|
ULONG FsNameOffset;
|
|
USHORT FsNameLength;
|
|
|
|
ULONG LabelOffset;
|
|
USHORT LabelLength;
|
|
|
|
ULONG SymbolicNamesOffset;
|
|
USHORT SymbolicNamesLength;
|
|
|
|
DWORD dwClusterSize;
|
|
|
|
} ASRFMT_CLUSTER_VOLUME_INFO, *PASRFMT_CLUSTER_VOLUME_INFO;
|
|
|
|
|
|
typedef struct _ASRFMT_CLUSTER_VOLUMES_TABLE {
|
|
|
|
DWORD DiskSignature;
|
|
|
|
DWORD NumberOfEntries;
|
|
|
|
ASRFMT_CLUSTER_VOLUME_INFO VolumeInfoEntry[1];
|
|
|
|
} ASRFMT_CLUSTER_VOLUMES_TABLE, *PASRFMT_CLUSTER_VOLUMES_TABLE;
|
|
|
|
|
|
BOOL
|
|
AsrpFmtGetVolumeDetails(
|
|
IN PWSTR lpVolumeGuid,
|
|
OUT PWSTR lpFsName,
|
|
IN DWORD cchFsName,
|
|
OUT PWSTR lpVolumeLabel,
|
|
IN DWORD cchVolumeLabel,
|
|
OUT LPDWORD lpClusterSize
|
|
)
|
|
{
|
|
DWORD dwFSFlags = 0,
|
|
dwSectorsPerCluster = 0,
|
|
dwBytesPerSector = 0,
|
|
dwNumFreeClusters = 0,
|
|
dwTotalNumClusters = 0;
|
|
|
|
BOOL result1 = TRUE,
|
|
result2 = TRUE;
|
|
|
|
*lpFsName = 0;
|
|
*lpVolumeLabel = 0;
|
|
*lpClusterSize = 0;
|
|
|
|
SetErrorMode(SEM_FAILCRITICALERRORS);
|
|
|
|
result1 = GetVolumeInformationW(lpVolumeGuid,
|
|
lpVolumeLabel,
|
|
cchVolumeLabel,
|
|
NULL, // no need for serial number
|
|
NULL, // max file name length
|
|
&dwFSFlags, // !! we might need to store some of this ...
|
|
lpFsName,
|
|
cchFsName
|
|
);
|
|
|
|
result2 = GetDiskFreeSpaceW(lpVolumeGuid,
|
|
&dwSectorsPerCluster,
|
|
&dwBytesPerSector,
|
|
&dwNumFreeClusters,
|
|
&dwTotalNumClusters
|
|
);
|
|
|
|
*lpClusterSize = dwSectorsPerCluster * dwBytesPerSector;
|
|
|
|
return (result1 && result2);
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
AsrpGetLocalVolumeInfo(
|
|
IN LPSTR lpszDeviceName,
|
|
IN LPSTR lpszContextString, // not used
|
|
OUT PVOID lpOutBuffer,
|
|
IN DWORD nOutBufferSize,
|
|
OUT LPDWORD lpBytesReturned
|
|
)
|
|
{
|
|
PASR_DISK_INFO pLocalDisk = NULL;
|
|
HANDLE heapHandle = NULL;
|
|
DWORD status = ERROR_SUCCESS;
|
|
BOOL result = FALSE;
|
|
ULONG MaxDeviceNumber = 0;
|
|
DWORD cchReqdSize = 0,
|
|
cchGuid = 0,
|
|
offset = 0,
|
|
index = 0,
|
|
i = 0;
|
|
|
|
USHORT
|
|
cbFsName = 0,
|
|
cbLabel = 0,
|
|
cbLinks = 0;
|
|
|
|
PMOUNTMGR_MOUNT_POINTS mountPointsOut = NULL;
|
|
|
|
WCHAR devicePath[MAX_PATH + 1];
|
|
WCHAR volumeGuid[MAX_PATH + 1];
|
|
WCHAR fileSystemName[MAX_PATH + 1];
|
|
WCHAR volumeLabel[MAX_PATH + 1];
|
|
UINT driveType = DRIVE_UNKNOWN;
|
|
DWORD clusterSize = 0;
|
|
|
|
BOOL bufferFull = FALSE,
|
|
foundGuid = FALSE;
|
|
|
|
PPARTITION_INFORMATION_EX currentPartitionEx = NULL;
|
|
PASRFMT_CLUSTER_VOLUMES_TABLE pTable = NULL;
|
|
|
|
heapHandle = GetProcessHeap();
|
|
|
|
//
|
|
// Either the BytesReturned must be non-null (he's getting the required size),
|
|
// or the lpOutBuffer must be non-null (he's getting the data).
|
|
//
|
|
_AsrpErrExitCode(!(lpOutBuffer || lpBytesReturned), status, ERROR_INVALID_PARAMETER);
|
|
if (lpBytesReturned) {
|
|
*lpBytesReturned = 0;
|
|
}
|
|
|
|
//
|
|
// Zero the out buffer
|
|
//
|
|
if ((lpOutBuffer) && (nOutBufferSize > 0)) {
|
|
ZeroMemory(lpOutBuffer, nOutBufferSize);
|
|
}
|
|
|
|
pLocalDisk = (PASR_DISK_INFO) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof (ASR_DISK_INFO)
|
|
);
|
|
_AsrpErrExitCode(!pLocalDisk, status, ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
cchReqdSize = MultiByteToWideChar(CP_ACP,
|
|
0,
|
|
lpszDeviceName,
|
|
-1,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
pLocalDisk->DevicePath = (PWSTR) HeapAlloc(
|
|
heapHandle,
|
|
HEAP_ZERO_MEMORY,
|
|
(cchReqdSize + 1) * (sizeof(WCHAR))
|
|
);
|
|
_AsrpErrExitCode(!(pLocalDisk->DevicePath), status, ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
result = MultiByteToWideChar(CP_ACP,
|
|
0,
|
|
lpszDeviceName,
|
|
-1,
|
|
pLocalDisk->DevicePath,
|
|
(cchReqdSize + 1)
|
|
);
|
|
_AsrpErrExitCode(!result, status, ERROR_INVALID_PARAMETER);
|
|
|
|
//
|
|
// Get the disk layout information
|
|
//
|
|
result = AsrpInitLayoutInformation(NULL, pLocalDisk, &MaxDeviceNumber, FALSE, FALSE); // basic info will suffice
|
|
_AsrpErrExitCode(!result, status, GetLastError());
|
|
_AsrpErrExitCode(!(pLocalDisk->pDriveLayoutEx), status, ERROR_SUCCESS);
|
|
|
|
//
|
|
//
|
|
//
|
|
offset = sizeof(ASRFMT_CLUSTER_VOLUMES_TABLE) +
|
|
(sizeof(ASRFMT_CLUSTER_VOLUME_INFO) * (pLocalDisk->pDriveLayoutEx->PartitionCount - 1));
|
|
pTable = (PASRFMT_CLUSTER_VOLUMES_TABLE) lpOutBuffer;
|
|
|
|
if ((!lpOutBuffer) || (offset > nOutBufferSize)) {
|
|
bufferFull = TRUE;
|
|
}
|
|
|
|
if (!bufferFull) {
|
|
|
|
if (PARTITION_STYLE_MBR == pLocalDisk->pDriveLayoutEx->PartitionStyle) {
|
|
pTable->DiskSignature = pLocalDisk->pDriveLayoutEx->Mbr.Signature;
|
|
}
|
|
else {
|
|
//
|
|
// At the moment, only MBR disks are cluster shared disks, and so
|
|
// we don't handle GPT disks here. If GPT disks are allowed to
|
|
// be on a shared bus in a cluster, change this.
|
|
//
|
|
_AsrpErrExitCode(FALSE, status, ERROR_SUCCESS);
|
|
}
|
|
|
|
pTable->NumberOfEntries = pLocalDisk->pDriveLayoutEx->PartitionCount;
|
|
}
|
|
|
|
|
|
for (index = 0; index < pLocalDisk->pDriveLayoutEx->PartitionCount; index++) {
|
|
|
|
currentPartitionEx = &(pLocalDisk->pDriveLayoutEx->PartitionEntry[index]);
|
|
mountPointsOut = NULL;
|
|
foundGuid = FALSE;
|
|
|
|
//
|
|
// For each partition, AsrpGetMountPoints gives a list of all mount points,
|
|
// then use that to AsrpFmtGetVolumeDetails
|
|
//
|
|
|
|
// get the volumeGuid
|
|
|
|
if (!(currentPartitionEx->PartitionNumber)) {
|
|
//
|
|
// Container partitions have partitionNumber = 0, and have no volume Guids.
|
|
//
|
|
continue;
|
|
}
|
|
|
|
memset(volumeGuid, 0, (MAX_PATH + 1) * sizeof(WCHAR));
|
|
swprintf(devicePath,
|
|
ASR_WSZ_DEVICE_PATH_FORMAT,
|
|
pLocalDisk->DeviceNumber,
|
|
currentPartitionEx->PartitionNumber
|
|
);
|
|
|
|
result = AsrpGetMountPoints(
|
|
devicePath,
|
|
(wcslen(devicePath) + 1)* sizeof(WCHAR), // including \0, in bytes
|
|
&mountPointsOut
|
|
);
|
|
if (!result || !(mountPointsOut)) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Go through the list of mount points, and pick out one that
|
|
// looks like a volume Guid (starts with \??\Volume)
|
|
//
|
|
cbLinks = sizeof(WCHAR); // \0 at the end
|
|
for (i = 0; i < mountPointsOut->NumberOfMountPoints; i++) {
|
|
|
|
PWSTR linkName = (PWSTR) (
|
|
((LPBYTE) mountPointsOut) +
|
|
mountPointsOut->MountPoints[i].SymbolicLinkNameOffset
|
|
);
|
|
|
|
USHORT sizeLinkName = (UINT) (mountPointsOut->MountPoints[i].SymbolicLinkNameLength);
|
|
|
|
if (!wcsncmp(ASR_WSZ_VOLUME_PREFIX, linkName, wcslen(ASR_WSZ_VOLUME_PREFIX)) &&
|
|
!foundGuid) {
|
|
wcsncpy(volumeGuid, linkName, sizeLinkName / sizeof(WCHAR));
|
|
foundGuid = TRUE;
|
|
}
|
|
|
|
cbLinks += sizeLinkName + (USHORT) sizeof(WCHAR);
|
|
}
|
|
|
|
//
|
|
// GetDriveType needs the volume guid in the dos-name-space, while the
|
|
// mount manager gives the volume guid in the nt-name-space. Convert
|
|
// the name by changing the \??\ at the beginning to \\?\, and adding
|
|
// a back-slash at the end.
|
|
//
|
|
cchGuid = wcslen(volumeGuid);
|
|
volumeGuid[1] = L'\\';
|
|
volumeGuid[cchGuid] = L'\\'; // Trailing back-slash
|
|
volumeGuid[cchGuid+1] = L'\0';
|
|
|
|
driveType = GetDriveTypeW(volumeGuid);
|
|
//
|
|
// Get the FS Label, cluster size, and so on.
|
|
//
|
|
result = AsrpFmtGetVolumeDetails(volumeGuid,
|
|
fileSystemName,
|
|
MAX_PATH + 1,
|
|
volumeLabel,
|
|
MAX_PATH + 1,
|
|
&clusterSize
|
|
);
|
|
if (!result) {
|
|
continue;
|
|
}
|
|
|
|
cbFsName = wcslen(fileSystemName) * sizeof(WCHAR);
|
|
cbLabel = wcslen(volumeLabel) * sizeof(WCHAR);
|
|
|
|
if (bufferFull) {
|
|
offset += (cbFsName + cbLabel + cbLinks);
|
|
}
|
|
else {
|
|
if (offset + cbFsName + cbLabel + cbLinks > nOutBufferSize) {
|
|
bufferFull = TRUE;
|
|
}
|
|
else {
|
|
|
|
if (cbFsName) {
|
|
CopyMemory(((LPBYTE)lpOutBuffer + offset),
|
|
fileSystemName,
|
|
cbFsName
|
|
);
|
|
pTable->VolumeInfoEntry[index].FsNameOffset = offset;
|
|
pTable->VolumeInfoEntry[index].FsNameLength = cbFsName;
|
|
offset += cbFsName;
|
|
}
|
|
|
|
if (cbLabel) {
|
|
CopyMemory(((LPBYTE)lpOutBuffer + offset),
|
|
volumeLabel,
|
|
cbLabel
|
|
);
|
|
pTable->VolumeInfoEntry[index].LabelOffset = offset;
|
|
pTable->VolumeInfoEntry[index].LabelLength = cbLabel;
|
|
offset += cbLabel;
|
|
}
|
|
|
|
//
|
|
// Copy the symbolic links, separated by zeroes
|
|
//
|
|
if (mountPointsOut->NumberOfMountPoints > 0) {
|
|
pTable->VolumeInfoEntry[index].SymbolicNamesOffset = offset;
|
|
}
|
|
|
|
for (i = 0; i < mountPointsOut->NumberOfMountPoints; i++) {
|
|
|
|
PWSTR linkName = (PWSTR) (
|
|
((LPBYTE) mountPointsOut) +
|
|
mountPointsOut->MountPoints[i].SymbolicLinkNameOffset
|
|
);
|
|
|
|
UINT sizeLinkName = (UINT) (mountPointsOut->MountPoints[i].SymbolicLinkNameLength);
|
|
|
|
CopyMemory(((LPBYTE)lpOutBuffer + offset),
|
|
linkName,
|
|
sizeLinkName
|
|
);
|
|
offset += (sizeLinkName + sizeof(WCHAR));
|
|
}
|
|
|
|
offset += sizeof(WCHAR); // second \0 at the end
|
|
pTable->VolumeInfoEntry[index].SymbolicNamesLength = cbLinks;
|
|
|
|
pTable->VolumeInfoEntry[index].driveType = driveType;
|
|
pTable->VolumeInfoEntry[index].PartitionNumber = currentPartitionEx->PartitionNumber;
|
|
pTable->VolumeInfoEntry[index].dwClusterSize = clusterSize;
|
|
}
|
|
}
|
|
|
|
_AsrpHeapFree(mountPointsOut);
|
|
|
|
}
|
|
|
|
if (lpBytesReturned) {
|
|
*lpBytesReturned = offset;
|
|
}
|
|
|
|
EXIT:
|
|
AsrpFreeStateInformation(&pLocalDisk, NULL);
|
|
|
|
return status;
|
|
}
|
|
|