/*++

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

#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
    )
{

    PASR_DISK_INFO pBuffer = (PASR_DISK_INFO) InBuffer;

/*    if (!((pBuffer->pDriveLayoutEx) && (pBuffer->pDiskGeometry) && (pBuffer->pPartition0Ex))) {
        return FALSE;
    }
*/

    pBuffer->IsPacked = TRUE;

    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);
    _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;
}


//
// ---- 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;

    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).
    //
    AsrpUnPackDiskInfo(outBuffer);

    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); // 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;
}