/*++ Copyright (c) 2000 Microsoft Corporation Module Name: mountpoints.c Abstract: This module processes mount point information for the disk resource DLL. Author: Steve Dziok (stevedz) 15-May-2000 Environment: User Mode Revision History: --*/ #define UNICODE 1 #include "disksp.h" #include "arbitrat.h" #include "newdisks.h" #include "newmount.h" #include #define SPACE_CHAR L' ' #define MAX_OFFSET_CHARS 80 // Maximum number of chars allowed in offset string #define LOG_CURRENT_MODULE LOG_MODULE_DISK extern PWCHAR GLOBALROOT_HARDDISK_PARTITION_FMT; // L"\\\\\?\\GLOBALROOT\\Device\\Harddisk%u\\Partition%u\\"; extern PWCHAR DEVICE_HARDDISK_PARTITION_FMT; // L"\\Device\\Harddisk%u\\Partition%u"; #ifndef ClusterHashGuid // // Hash a GUID to a ULONG value. // #define ClusterHashGuid(_guid) ( ((PULONG) &(_guid))[0] ^ ((PULONG) &(_guid))[1] ^ \ ((PULONG) &(_guid))[2] ^ ((PULONG) &(_guid))[3] ) #endif #define MPS_ENABLED 0x00000001 #define MPS_DELETE_INVALID_MPS 0x00000002 // Not currently used #define MPS_NONCLUSTERED_TO_CLUSTERED_MPS 0x00000010 // Not currently used #define MPS_KEEP_EXISTING_MPS 0x00000020 // Not currently used #define MPS_IGNORE_MAX_VOLGUIDS 0x00000100 // Assume 32 nodes with 8 partitions per disk. #define MAX_ALLOWED_VOLGUID_ENTRIES_PER_DISK 32 * 8 #define STOP_CLUSTER_ENUMERATIONS ERROR_INVALID_PRINTER_COMMAND #define PHYSICAL_DISK_WSTR L"Physical Disk" #define CHAR_TO_REPLACE 2 #define MOUNTDEV_WSZ_VOLUME_GUID_PREFIX L"\\??\\Volume{" // Forms: \??\Volume{ #define MOUNTDEV_CB_VOLUME_GUID_PREFIX 22 #define MOUNTDEV_LOOKS_LIKE_VOLUME_GUID(name, length) \ ((length > MOUNTDEV_CB_VOLUME_GUID_PREFIX) && \ (!memcmp(name, MOUNTDEV_WSZ_VOLUME_GUID_PREFIX, MOUNTDEV_CB_VOLUME_GUID_PREFIX))) #define MOUNTDEV_WSZ_ALT_VOLUME_GUID_PREFIX L"\\\\?\\Volume{" // Forms: \\?\Volume{ #define MOUNTDEV_CB_ALT_VOLUME_GUID_PREFIX 22 #define MOUNTDEV_LOOKS_LIKE_ALT_VOLUME_GUID(name, bytelength) \ ((bytelength > MOUNTDEV_CB_ALT_VOLUME_GUID_PREFIX) && \ (!memcmp(name, MOUNTDEV_WSZ_ALT_VOLUME_GUID_PREFIX, MOUNTDEV_CB_ALT_VOLUME_GUID_PREFIX))) typedef struct _SIG_INFO { DWORD Signature; BOOL Clustered; } SIG_INFO, *PSIG_INFO; typedef struct _DEPENDENCY_INFO { DWORD SrcSignature; DWORD TargetSignature; BOOL DependencyCorrect; } DEPENDENCY_INFO, *PDEPENDENCY_INFO; typedef struct _STR_LIST { LPWSTR MultiSzList; // REG_MULTI_SZ string DWORD ListBytes; // Number of bytes, not number of WCHARs! } STR_LIST, *PSTR_LIST; DWORD AddStrToList( IN PDISK_RESOURCE ResourceEntry, IN PWSTR NewStr, IN DWORD PartitionNumber, IN OUT PSTR_LIST StrList ); DWORD AssignDevice( HANDLE MountMgrHandle, PWCHAR MountName, PWCHAR VolumeDevName ); DWORD CheckDependencies( DWORD SrcSignature, DWORD TargetSignature, PBOOL DependencyCorrect ); VOID CheckMPsForVolume( IN OUT PDISK_RESOURCE ResourceEntry, IN PWSTR VolumeName ); DWORD CheckSignatureClustered( DWORD Signature, PBOOL IsClustered ); DWORD CreateVolGuidList( IN OUT PDISK_RESOURCE ResourceEntry ); DWORD DeleteVolGuidList( PDISK_RESOURCE ResourceEntry ); DWORD DependencyCallback( RESOURCE_HANDLE hOriginal, RESOURCE_HANDLE hResource, PVOID lpParams ); #if DBG VOID DumpDiskInfoParams( PDISK_RESOURCE ResourceEntry ); #endif DWORD EnumSigDependencies( RESOURCE_HANDLE DependentResource, DWORD DependsOnSignature, PBOOL DependencyCorrect ); DWORD GetMountPoints( PWSTR VolumeName, PWSTR *VolumePaths ); BOOL GetOffsetFromPartNo( DWORD PartitionNo, PMOUNTIE_INFO Info, PLARGE_INTEGER Offset ); BOOL GetPartNoFromOffset( PLARGE_INTEGER Offset, PMOUNTIE_INFO Info, PDWORD PartitionNumber ); DWORD GetSignatureForVolume( PDISK_RESOURCE ResourceEntry, PWSTR Volume, PDWORD Signature ); DWORD GetSignatureFromDiskInfo( RESOURCE_HANDLE hResource, DWORD *dwSignature ); BOOL IsMountPointAllowed( PWSTR MpName, PWSTR TargetVol, PDISK_RESOURCE ResourceEntry ); BOOL MPIsDriveLetter( IN PWSTR MountPoint ); VOID PrintStrList( PDISK_RESOURCE ResourceEntry, LPWSTR MultiSzList, DWORD ListBytes ); DWORD ProcessVolGuidList( IN OUT PDISK_RESOURCE ResourceEntry ); static DWORD SetMPListThread( LPVOID lpThreadParameter ); DWORD SetupVolGuids( IN OUT PDISK_RESOURCE ResourceEntry ); DWORD SigInfoCallback( RESOURCE_HANDLE hOriginal, RESOURCE_HANDLE hResource, PVOID lpParams ); DWORD ValidateListOffsets( IN OUT PDISK_RESOURCE ResourceEntry, IN PWSTR MasterList ); DWORD ValidateMountPoints( IN OUT PDISK_RESOURCE ResourceEntry ); DWORD WrapClusterResourceControl( RESOURCE_HANDLE hResource, DWORD dwControlCode, LPVOID *OutBuffer, DWORD *dwBytesReturned ); DWORD DisksProcessMountPointInfo( IN OUT PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: During online processing, find all mount points directed towards this volume (identified by the ResourceEntry), and process the VolGuid list for this volume. If the VolGuid list exists in the cluster database, use it. Otherwise, get the current VolGuid and add it to the VolGuid list. VolGuid list is of the form: SectorOffset1 VolGuid1 SectorOffset1 VolGuid2 SectorOffset1 VolGuid3 SectorOffset2 VolGuid1 SectorOffset2 VolGuid2 SectorOffset3 VolGuid1 ... ... There are three possible mount point configurations involving clustered disks (we are not concerned about nonshared disks pointing to nonshared disks): Source --> Target ----------------- ----------------- 1. clustered disk clustered disk 2. nonclustered disk clustered disk 3. clustered disk nonclustered disk Only configuration (1) is supported. Configurations (2) and (3) are not supported. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: ERROR_NOT_READY - MPInfo structure not yet initialized. Win32 error code. --*/ { DWORD dwError = NO_ERROR; // // Mount point structures not initialized (i.e. critical section). Don't continue. // if ( !ResourceEntry->MPInfo.Initialized ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"DisksProcessMountPointInfo: Mount point information not initialized. \n" ); return ERROR_NOT_READY; } #if USEMOUNTPOINTS_KEY // // Mount point support disabled, don't do anything. // if ( !( ResourceEntry->DiskInfo.Params.UseMountPoints & MPS_ENABLED ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"DisksProcessMountPointInfo: Mount point processing disabled via registry \n" ); // // Delete the VolGuid list if it exists and remove this info // from the cluster database. // dwError = DeleteVolGuidList( ResourceEntry ); if ( ERROR_SHARING_PAUSED == dwError ) { PostMPInfoIntoRegistry( ResourceEntry ); } dwError = NO_ERROR; return dwError; } #endif // // Check if we are currently processing mount point info. If so, exit with an error. // if ( InterlockedCompareExchange( &ResourceEntry->MPInfo.MPListCreateInProcess, 1, 0 ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"DisksProcessMountPointInfo: MPList creation in process, bypassing request \n" ); return ERROR_BUSY; } __try { dwError = ProcessVolGuidList( ResourceEntry ); ValidateMountPoints( ResourceEntry ); // Fall through... #if 0 // Add code similar to this when MPs from nonclustered to clustered disks is supported. if ( ( ResourceEntry->DiskInfo.Params.UseMountPoints & MPS_ENABLED ) && ( ResourceEntry->DiskInfo.Params.UseMountPoints & MPS_NONCLUSTERED_TO_CLUSTERED_MPS ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"DisksProcessMountPointInfo: ProcessMPList \n" ); dwError = ProcessMPListConfig2( ResourceEntry ); #endif } __finally { InterlockedExchange( &ResourceEntry->MPInfo.MPListCreateInProcess, 0 ); } return dwError; } // DisksProcessMountPointInfo DWORD ProcessVolGuidList( IN OUT PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Main routine to create a new VolGuid list or to process an existing VolGuid list. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: Win32 error code. --*/ { DWORD dwError = NO_ERROR; __try { // // Always try to add the current VolGuid to the list. // dwError = CreateVolGuidList( ResourceEntry ); if ( NO_ERROR != dwError ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"ProcessVolGuidList: Stop processing VolGuid list, Create failed %1!u! \n", dwError ); __leave; } // // If the list is empty here (it shouldn't be), then exit with an error. // if ( !ResourceEntry->DiskInfo.Params.MPVolGuids || 0 == ResourceEntry->DiskInfo.Params.MPVolGuidsSize ) { dwError = ERROR_INVALID_DATA; __leave; } PrintStrList( ResourceEntry, ResourceEntry->DiskInfo.Params.MPVolGuids, ResourceEntry->DiskInfo.Params.MPVolGuidsSize ); // // Make sure the offsets are correct in the VolGuid list. // Note that it is possible for the list to be deleted and // recreated after this validation, but that is not a problem (because // when they are recreated they will have the correct offsets). // dwError = ValidateListOffsets( ResourceEntry, ResourceEntry->DiskInfo.Params.MPVolGuids ); if ( ERROR_INVALID_DATA == dwError ) { // // At least one of the offsets is invalid. Possibly, the partition // layout on the disk has been changed. Delete the existing // list, and create a new one. // // This code should run infrequently... // // The partition layout might change if ASR runs and creates new partitions // that don't match the previous system exactly. Since NTBACKUP saves the // cluster DB information, the mount point list will be restored but won't // match the actual "new" partition layout. ASR will insure that all the // mount points and VolGuids on the system are created, so we should be able // to simply delete and recreate the mount point list. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"ProcessVolGuidList: Invalid offset in existing VolGuid list. Deleting and recreating. \n" ); DeleteVolGuidList( ResourceEntry ); dwError = CreateVolGuidList( ResourceEntry ); __leave; } else if ( ERROR_INSUFFICIENT_BUFFER == dwError ) { // // The Volguid list is too large and likely corrupt. We cannot // proceed. // __leave; } // // For every VolGuid in the list, make sure they are assigned to the correct // volumes on this system. // dwError = SetupVolGuids( ResourceEntry ); } __finally { } return dwError; } // ProcessVolGuidList DWORD CreateVolGuidList( IN OUT PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Add the current system VolGuid to the list. If the list is empty, create a new list. For each partition on this disk (identified by the ResourceEntry), get the byte offset of that partition. Get the current VolGuid for that parition, and add it to the list. If the current VolGuid is already in the list, ignore the error. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: ERROR_INVALID_DATA - partition info in disk resource is invalid Win32 error code. --*/ { PMOUNTIE_PARTITION entry; HANDLE mountMgrHandle = INVALID_HANDLE_VALUE; DWORD dwError = ERROR_SUCCESS; DWORD nPartitions = MountiePartitionCount( &ResourceEntry->MountieInfo ); DWORD physicalDrive = ResourceEntry->DiskInfo.PhysicalDrive; DWORD idx; DWORD volumeNameLenChars; // Number of characters DWORD newStrListLenBytes; // Number of bytes WCHAR szGlobalDiskPartName[MAX_PATH]; WCHAR szVolumeName[MAX_PATH]; STR_LIST newStrList; BOOL freeNewList = TRUE; __try { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: Adding current VolGuid to list \n" ); EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); ZeroMemory( szGlobalDiskPartName, sizeof(szGlobalDiskPartName) ); ZeroMemory( szVolumeName, sizeof(szVolumeName) ); ZeroMemory( &newStrList, sizeof(STR_LIST) ); dwError = DevfileOpen( &mountMgrHandle, MOUNTMGR_DEVICE_NAME ); if ( dwError != NO_ERROR ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"CreateVolGuidList: Failed to open MountMgr %1!u! \n", dwError ); __leave; } newStrListLenBytes = ResourceEntry->DiskInfo.Params.MPVolGuidsSize; if ( newStrListLenBytes ) { newStrList.MultiSzList = LocalAlloc( LPTR, newStrListLenBytes ); if ( !(newStrList.MultiSzList) ) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"CreateVolGuidList: Failed to allocate storage for new list %1!u! \n", dwError ); __leave; } // // Copy current list to newStrList. // memcpy( newStrList.MultiSzList, ResourceEntry->DiskInfo.Params.MPVolGuids, newStrListLenBytes ); newStrList.ListBytes = newStrListLenBytes; #if 0 // Useful for debugging (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: newStrListLen %1!u! \n", newStrList.ListBytes ); PrintStrList( ResourceEntry, newStrList.MultiSzList, newStrList.ListBytes ); #endif } else { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: Current list empty, creating new list \n" ); } // // Check each interesting partition. Since only "valid" partitions are // saved in the MountieInfo structure, we will only look at those (ignoring those // partitions that are not NTFS). // for ( idx = 0; idx < nPartitions; ++idx ) { entry = MountiePartition( &ResourceEntry->MountieInfo, idx ); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: index %1!u! entry %2!x! \n", idx, entry ); #endif if ( !entry ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"CreateVolGuidList: no partition entry for index %1!u! \n", idx ); // // Something bad happened to our data structures. // dwError = ERROR_INVALID_DATA; __leave; } // // Create the device name of the form: // \\?\GLOBALROOT\Device\HarddiskX\PartitionY\ (uses trailing backslash) // _snwprintf( szGlobalDiskPartName, MAX_PATH, // Number of CHARS, not bytes GLOBALROOT_HARDDISK_PARTITION_FMT, physicalDrive, entry->PartitionNumber ); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: Using name (%1!ws!) \n", szGlobalDiskPartName ); #endif // // Get the current VolGuid for this partition. // if ( !GetVolumeNameForVolumeMountPointW( szGlobalDiskPartName, szVolumeName, sizeof(szVolumeName)/sizeof(WCHAR) )) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"CreateVolGuidList: GetVolumeNameForVolumeMountPoint for (%1!ws!) returned %2!u!\n", szGlobalDiskPartName, dwError ); // Try next partition. continue; } #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: Returned volume name (%1!ws!) \n", szVolumeName ); #endif // // Fix current VolGuid name. // // GetVolumeNameForVolumeMountPoint returns name of the form: // \\?\Volume{-GUID-}\ // // But we need the name to be in the form: // \??\Volume{-GUID-} // volumeNameLenChars = wcslen( szVolumeName ); if ( !(MOUNTDEV_LOOKS_LIKE_ALT_VOLUME_GUID( szVolumeName, volumeNameLenChars * sizeof(WCHAR) ) ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"CreateVolGuidList: Improper volume name format (%1!ws!) \n", szVolumeName ); // Try next partition. continue; } szVolumeName[1] = L'?'; if ( L'\\' == szVolumeName[volumeNameLenChars-1]) { szVolumeName[volumeNameLenChars-1] = L'\0'; } #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: Fixed volume name (%1!ws!) \n", szVolumeName ); #endif // // Add the new string to the list. If the new string is already in the list, this // routine won't do anything and will return NO_ERROR. // dwError = AddStrToList( ResourceEntry, szVolumeName, entry->PartitionNumber, &newStrList ); if ( NO_ERROR != dwError ) { __leave; } } // // Optimization: // If the new list is the same as the old list, we are done. // if ( newStrList.MultiSzList && newStrList.ListBytes && newStrList.ListBytes == ResourceEntry->DiskInfo.Params.MPVolGuidsSize && ( 0 == memcmp( newStrList.MultiSzList, ResourceEntry->DiskInfo.Params.MPVolGuids, newStrListLenBytes ) ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: New list same as old list, skipping update \n" ); __leave; } (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"CreateVolGuidList: Saving new VolGuid list \n" ); freeNewList = FALSE; // // The new list is different from the old list. Free the old list, update // the ResourceEntry with the new list information, and update the cluster // database. // DeleteVolGuidList( ResourceEntry ); EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); ResourceEntry->DiskInfo.Params.MPVolGuids = newStrList.MultiSzList; ResourceEntry->DiskInfo.Params.MPVolGuidsSize = newStrList.ListBytes; LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); PrintStrList( ResourceEntry, ResourceEntry->DiskInfo.Params.MPVolGuids, ResourceEntry->DiskInfo.Params.MPVolGuidsSize ); dwError = PostMPInfoIntoRegistry( ResourceEntry ); } __finally { if ( INVALID_HANDLE_VALUE != mountMgrHandle ) { CloseHandle( mountMgrHandle ); } if ( freeNewList && newStrList.MultiSzList ) { LocalFree( newStrList.MultiSzList ); } LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); } return dwError; } // CreateVolGuidList DWORD AddStrToList( IN PDISK_RESOURCE ResourceEntry, IN PWSTR NewStr, IN DWORD PartitionNumber, IN OUT PSTR_LIST StrList ) /*++ Routine Description: Add the string to the MULTI_SZ list. Convert the partition number to a byte offset so we don't rely on partition numbers. List format will be: ByteOffset1 Str1 ByteOffset1 Str2 ByteOffset1 Str3 ByteOffset2 Str1 ByteOffset2 Str2 ByteOffset3 Str1 ... ... Arguments: Return Value: --*/ { PWCHAR listEntry = NULL; DWORD listEntrySizeChars; DWORD lenChars; DWORD newStrLenChars; DWORD listChars; DWORD remainingLen; DWORD dwError = ERROR_INVALID_DATA; LARGE_INTEGER offset; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"AddStrToList: Adding str (%1!ws!) \n", NewStr ); #endif newStrLenChars = wcslen( NewStr ); if ( 0 == newStrLenChars ) { // // Something wrong with the string length. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"AddStrToList: Invalid length: NewStrLen = %1!u! \n", newStrLenChars ); goto FnExit; } // // Indicate an error unless we can allocate and copy the info // into the list. Calculate the minimum size needed, then get // larger buffer. This buffer is temporary and freed later. // listEntrySizeChars = ( newStrLenChars + // Char length of parameter string MAX_OFFSET_CHARS + // Char length of offset string 1 + // Room to change end of offset string to space and extend it 1 ) // Unicode NULL * 2; // Make sure buffer is large enough listEntry = LocalAlloc( LPTR, listEntrySizeChars * sizeof(WCHAR) ); if ( !listEntry ) { dwError = GetLastError(); goto FnExit; } // // Get the offset for the specified partition. // if ( !GetOffsetFromPartNo( PartitionNumber, &ResourceEntry->MountieInfo, &offset ) ) { // // Can't get the offset for the specified partition. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"AddStrToList: GetOffsetFromPartNo failed \n" ); goto FnExit; } // // Convert the offset into a string. Put the offset into listEntry. // _ui64tow( offset.QuadPart, listEntry, 16 ); lenChars = wcslen( listEntry ); if ( 0 == lenChars || lenChars >= MAX_OFFSET_CHARS ) { // // The length of the offset string is invalid. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"AddStrToList: Invalid offset string length = %1!u! \n", lenChars ); goto FnExit; } // Format will be: // ByteOffset1 Str1 // ByteOffset1 Str2 // ByteOffset1 Str3 // ByteOffset2 Str1 // ByteOffset2 Str2 // ByteOffset3 Str1 // ... ... // // Change the end of the offset string to another character. Move the end of string // out one character. This extra space was included when we allocated the buffer. // listEntry[lenChars+1] = UNICODE_NULL; listEntry[lenChars] = SPACE_CHAR; // // One more check. Make sure enough space remaining for adding string. // remainingLen = listEntrySizeChars - wcslen( listEntry ) - 1; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"AddStrToList: New string length %1!u! Remaining list entry length %2!u! \n", newStrLenChars, remainingLen ); #endif if ( newStrLenChars >= remainingLen ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"AddStrToList: New string length %1!u! larger than remaining list entry length %2!u! \n", newStrLenChars, remainingLen ); goto FnExit; } // // Put the rest of the string in list entry. // wcsncat( listEntry, NewStr, newStrLenChars ); // // If the string is already in the list, skip it. // if ( ClRtlMultiSzScan( ResourceEntry->DiskInfo.Params.MPVolGuids, listEntry ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"AddStrToList: Skipping duplicate entry (%1!ws!) \n", listEntry ); dwError = NO_ERROR; goto FnExit; } // // Note that ClRtlMultiSzAppend updates the number of CHARACTERS, but we // need to have the number of BYTES in the property table. We will adjust // this value later. // listChars = StrList->ListBytes / sizeof(WCHAR); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"AddStrToList: StrList->MultiSzList at %1!x!, numBytes %2!u! \n", StrList->MultiSzList, StrList->ListBytes ); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"AddStrToList: Adding str entry: (%1!ws!) numChars %2!u! \n", listEntry, listChars ); #endif dwError = ClRtlMultiSzAppend( &(StrList->MultiSzList), &listChars, listEntry ); // // Convert the number of CHARACTERS back to bytes. // StrList->ListBytes = listChars * sizeof(WCHAR); if ( ERROR_SUCCESS == dwError) { #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"AddStrToList: Added str, numBytes %1!u! numChars %2!u! \n", StrList->ListBytes, listChars ); #endif } else { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"AddStrToList: Unable to append MultiSz string for (%1!ws!), failed %x \n", NewStr, dwError ); } FnExit: if ( listEntry ) { LocalFree( listEntry ); } return dwError; } // AddStrToList DWORD SetupVolGuids( IN OUT PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Add every VolGuid in the existing MULTI_SZ VolGuid list to the system. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: Win32 error code. --*/ { PWCHAR currentStr; PWCHAR volGuid; HANDLE mountMgrHandle = INVALID_HANDLE_VALUE; DWORD physicalDrive = ResourceEntry->DiskInfo.PhysicalDrive; DWORD currentStrLenChars = 0; DWORD dwError = NO_ERROR; DWORD partitionNo; DWORD count; LARGE_INTEGER offset; WCHAR szDiskPartName[MAX_PATH]; __try { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetupVolGuids: Processing VolGuid list \n" ); EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); dwError = DevfileOpen( &mountMgrHandle, MOUNTMGR_DEVICE_NAME ); if ( dwError != NO_ERROR ) { __leave; } // // Parse through the list. // for ( currentStr = (PWCHAR)ResourceEntry->DiskInfo.Params.MPVolGuids, currentStrLenChars = wcslen( currentStr ) ; currentStrLenChars ; currentStr += currentStrLenChars + 1, currentStrLenChars = wcslen( currentStr ) ) { offset.QuadPart = 0; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetupVolGuids: CurrentStr (%1!ws!), numChars %2!u! \n", currentStr, currentStrLenChars ); #endif // // Convert the offset from a string to a large integer value. // count = swscanf( currentStr, L"%I64x ", &offset.QuadPart ); if ( 0 == count ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"SetupVolGuids: Unable to parse offset from currentStr (%1!ws!) \n", currentStr ); continue; } // // Convert the offset to a partition number. // if ( !GetPartNoFromOffset( &offset, &ResourceEntry->MountieInfo, &partitionNo ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"SetupVolGuids: Unable to convert offset ( %1!08X!%2!08X! ) to partition number \n", offset.HighPart, offset.LowPart ); // couldn't get !I64X! to work... continue; } // // Get a pointer to the VolGuid data, just after the byte offset. // volGuid = wcsstr( currentStr, MOUNTDEV_WSZ_VOLUME_GUID_PREFIX ); if ( !volGuid ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"SetupVolGuids: Unable to find volume string in current list entry (%1!ws) \n", currentStr ); continue; } #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetupVolGuids: Using VolGuid (%1!ws!) \n", volGuid ); #endif ZeroMemory( szDiskPartName, sizeof( szDiskPartName ) ); // // Create the device name of the form: // \Device\HarddiskX\PartitionY (no trailing backslash) // _snwprintf( szDiskPartName, MAX_PATH, // Number of CHARS, not bytes DEVICE_HARDDISK_PARTITION_FMT, physicalDrive, partitionNo ); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetupVolGuids: Using device name (%1!ws!) \n", szDiskPartName ); #endif dwError = AssignDevice( mountMgrHandle, volGuid, szDiskPartName ); if ( NO_ERROR != dwError && STATUS_OBJECT_NAME_COLLISION != dwError ) { // Assign device will return: 0xC0000035 STATUS_OBJECT_NAME_COLLISION // if we are setting a VolGuid that was previously set. This is not // a problem. (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"SetupVolGuids: Unable to assign VolGuid to device, error %1!x! \n", dwError ); // Continue processing with error... } dwError = STATUS_SUCCESS; } } __finally { if ( INVALID_HANDLE_VALUE != mountMgrHandle ) { CloseHandle( mountMgrHandle ); } LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); } return dwError; } // SetupVolGuids DWORD AssignDevice( HANDLE MountMgrHandle, PWCHAR MountName, PWCHAR VolumeDevName ) /*++ Routine Description: Put the specified MountName (i.e. mount point name) into the mount manager's internal table of mount points. Inputs: MountMgrHandle - Handle to the mount manager. The caller is responsible for opening and closing this handle. MountName - Mountpoint name of the form: \??\Volume{-GUID-} - note prefix "\??\" and no trailing backslash. \DosDevices\X: - works if a drive letter is not already assigned VolumeDevName - Volume device name. Can be one of the following forms (note that case is important). The "#" is a zero-based device number (and partition number as appropriate). \Device\CdRom# \Device\Floppy# \Device\HarddiskVolume# \Device\Harddisk#\Partition# Return value: A Win32 error code. --*/ { PMOUNTMGR_CREATE_POINT_INPUT input; DWORD status; USHORT mountNameLenBytes; USHORT volumeDevNameLenBytes; USHORT inputlengthBytes; if ( INVALID_HANDLE_VALUE == MountMgrHandle ) { return ERROR_INVALID_PARAMETER; } mountNameLenBytes = wcslen( MountName ) * sizeof(WCHAR); volumeDevNameLenBytes = wcslen( VolumeDevName ) * sizeof(WCHAR); inputlengthBytes = sizeof(MOUNTMGR_CREATE_POINT_INPUT) + mountNameLenBytes + volumeDevNameLenBytes; input = (PMOUNTMGR_CREATE_POINT_INPUT)LocalAlloc( LPTR, inputlengthBytes ); if ( !input ) { return ERROR_NOT_ENOUGH_MEMORY; } input->SymbolicLinkNameOffset = sizeof(MOUNTMGR_CREATE_POINT_INPUT); input->SymbolicLinkNameLength = mountNameLenBytes; input->DeviceNameOffset = input->SymbolicLinkNameOffset + input->SymbolicLinkNameLength; input->DeviceNameLength = volumeDevNameLenBytes; RtlCopyMemory((PCHAR)input + input->SymbolicLinkNameOffset, MountName, mountNameLenBytes); RtlCopyMemory((PCHAR)input + input->DeviceNameOffset, VolumeDevName, volumeDevNameLenBytes); status = DevfileIoctl( MountMgrHandle, IOCTL_MOUNTMGR_CREATE_POINT, input, inputlengthBytes, NULL, 0, NULL ); LocalFree( input ); return status; } // AssignDevice DWORD DeleteVolGuidList( PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Delete the list from the DISK_RESOURCE structure, if it exists (free the memmory). Also deletes the information from the cluster database. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: NO_ERROR - The list was deleted. ERROR_NOT_READY - The mount point information was not yet initialized. Win32 error code. --*/ { DWORD dwError = NO_ERROR; // // Mount point structures not initialized (i.e. critical section). Don't continue. // if ( !ResourceEntry->MPInfo.Initialized ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"DeleteVolGuidList: Mount point info not initialized. List not deleted. \n" ); dwError = ERROR_NOT_READY; goto FnExit; } EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); // // If existing list, free it. // if ( ResourceEntry->DiskInfo.Params.MPVolGuids ) { LocalFree( ResourceEntry->DiskInfo.Params.MPVolGuids ); ResourceEntry->DiskInfo.Params.MPVolGuidsSize = 0; ResourceEntry->DiskInfo.Params.MPVolGuids = NULL; dwError = ClusterRegDeleteValue( ResourceEntry->ResourceParametersKey, CLUSREG_NAME_PHYSDISK_MPVOLGUIDS ); // // If the update failed and the disk is not yet online, it will fail with // ERROR_SHARING_PAUSED. Just return the error. If the caller really, // really, really wants the cluster database cleaned up, they can // use the PostMPInfoIntoRegistry call to create a thread to do this // work. if ( NO_ERROR != dwError ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"DeleteVolGuidList: Unable to delete VolGuid from cluster database %1!u! \n", dwError ); } } LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); FnExit: #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"DeleteVolGuidList: returns %1!u! \n", dwError ); #endif return dwError; } // DeleteVolGuidList BOOL IsMountPointAllowed( PWSTR MpName, PWSTR TargetVol, PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Verify that the mount point is allowed. There are several reasons why the mount point would not be allowed. At this point, the source volume will be accessible. If the source were offline, we wouldn't even know about it, and we wouldn't even get to this routine. The fact that the source is accessible allows us to do some things differently (i.e. we can talk to the disk if needed). Dependencies: If source disk S has a mount point to target disk T ( S:\tdir --> T: ), then source S is dependent on target T and target T must be brought online before source S is online. Quorum drive restrictions: Generally, the quorum drive cannot have a mount point to another disk. This is because the quorum drive is not allowed to be dependent on another resource. A mount point from clustered source S to quorum target Q is allowed. The dependency requirement is that quorum drive Q must come online before source drive S (i.e. S depends on Q). A mount point from quorum source Q to a clustered target T is not allowed because this would require T to be online before Q (i.e. Q depnends on T), and the quorum drive cannot have dependencies. A mount point from quorum source Q to a nonclustered target C is invalid. Mount points to non-clustered disks: These types of mountpoints should not be used. Configurations supported: C is a non-clustered disk. X, Y are clustered disks, not quorum disks. Q is quorum disk. Source Target Status ------ ------ ------------------------------------------------------------ C --> Q Not supported. Log error to system event log. C --> X Not supported. Log error to system event log. X --> C Not supported. We never process non-clustered target C. X --> Q Valid if drive X is dependent on drive Q (same group). X --> Y Valid if drive X is dependent on drive Y (Y online first). Q --> X Invalid. Quorum drive cannot be dependent on another resource. Q --> C Not supported. We never process target C. Future work: Log event message when invalid mount point used. Arguments: MpName - Possible mount point. This will either be a mount point or a drive letter (which is actually a mount point). Format can be: x:\ [Note trailing backslash!] x:\some-mp-name\ [Note trailing backslash!] x:\some-dir\some-mp-name\ [Note trailing backslash!] TargetVol - Mount point target volume name. Format: \\?\Volume{GUID}\ [Note trailing backslash!] ResourceEntry - Pointer to the DISK_RESOURCE structure for the target volume. Return Value: TRUE if the mount point is allowed. --*/ { DWORD srcSignature; DWORD targetSignature = ResourceEntry->DiskInfo.Params.Signature; DWORD quorumSignature; DWORD dwError = NO_ERROR; DWORD messageId = 0; BOOL mpAllowed = TRUE; BOOL srcSigIsClustered; BOOL dependencyCorrect; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"IsMountPointAllowed: MP source (%1!ws!) \n", MpName ); #endif // // Since the drive letter is also a mountpoint, a drive letter is valid. // if ( MPIsDriveLetter( MpName ) ) { #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"IsMountPointAllowed: Valid MP: MP is a drive letter \n" ); #endif mpAllowed = TRUE; goto FnExit; } // // Get the signature of the source drive. This drive is accessible (or // we wouldn't even have the mount point info yet) but we cannot assume it // is a clustered drive. If this fails, we can't use the mountpoint. // dwError = GetSignatureForVolume( ResourceEntry, MpName, &srcSignature ); if ( NO_ERROR != dwError || !srcSignature ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"IsMountPointAllowed: Unable to get signature from volume, error %1!u! \n", dwError ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_SIG_UNAVAILABLE; goto FnExit; } // // If source points back to target, this mount point is not allowed. Even though // the mount point code seems to allow this, there are some strange circular // dependencies that show up. // if ( srcSignature == targetSignature ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"IsMountPointAllowed: Invalid MP: Source and target volumes are the same device \n" ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_SOURCE_EQUAL_TARGET; goto FnExit; } // // If we can't enumerate the cluster disk signatures, assume that this mount // point is not allowed. // dwError = CheckSignatureClustered( srcSignature, &srcSigIsClustered ); if ( NO_ERROR != dwError ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"IsMountPointAllowed: Unable to enumerate disk signatures, error %1!u! \n", dwError ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_SIG_ENUMERATION_FAILED; goto FnExit; } // // In the future, a non-clustered source might be valid only if a special registry // key is specified - this is not currently implemented. So even though this will // work without us doing anything (on this one node - when the disk is moved to // another node, the MP won't work), we will warn the user that it is invalid. // // The user would have to manually set these types of mountpoints on each node. // // C --> X - Invalid, but should work // C --> Q - Invalid, but should work if ( !srcSigIsClustered ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"IsMountPointAllowed: Invalid MP: Source volume is non-clustered \n" ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_SOURCE_NOT_CLUSTERED; goto FnExit; } // // At this point, the source drive is a clustered drive but we don't yet know // what the target looks like. // // X --> ?? // Q --> ?? // // Get quorum signature. If this fails, assume the mount point is not allowed. // dwError = GetQuorumSignature( &quorumSignature ); if ( NO_ERROR != dwError ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"IsMountPointAllowed: Unable to get quorum signature, error %1!u! \n", dwError ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_QUORUM_SIG_UNAVAILABLE; goto FnExit; } // // If the source is the quorum drive, the mount point is not allowed because // the quorum cannot be dependent on another disk resource. We already know // that the source and target are different devices from an ealier check. // if ( quorumSignature == srcSignature ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"IsMountPointAllowed: Invalid MP: source sig %1!x! is quorum disk, target sig %2!x! is clustered \n", srcSignature, targetSignature ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_SOURCE_IS_QUORUM; goto FnExit; } #if 0 // This config is valid only if the dependencies are correct between x and q. Fall through // to check dependencies. // // If the target is the quorum drive and the source and target are different, then // this mount point is allowed. // // X --> Q if ( quorumSignature == targetSignature && srcSignature != targetSignature ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"IsMountPointAllowed: Valid MP: source sig %1!x! is clustered, target sig %2!x! is quorum disk \n", srcSignature, targetSignature ); mpAllowed = TRUE; goto FnExit; } #endif // // We have two possibilities left: // // X --> Q - valid // X --> Y - valid // // // These are valid only if the dependencies are set up correctly. Y must // be online before X, so X is dependent on Y. // dependencyCorrect = FALSE; dwError = CheckDependencies( srcSignature, targetSignature, &dependencyCorrect ); if ( NO_ERROR != dwError ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"IsMountPointAllowed: Unable to enumerate disk dependencies, error %1!u! \n", dwError ); mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_ENUM_DISK_DEP_FAILED; goto FnExit; } if ( dependencyCorrect ) { #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"IsMountPointAllowed: Valid MP: Dependencies are correct \n" ); #endif mpAllowed = TRUE; goto FnExit; } (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"IsMountPointAllowed: Invalid MP: Dependencies are incorrect \n", MpName ); // // If we get here, the mount point is not allowed. // mpAllowed = FALSE; messageId = RES_DISK_INVALID_MP_INVALID_DEPENDENCIES; FnExit: if ( !mpAllowed && messageId ) { // Log event... ClusResLogSystemEventByKey2(ResourceEntry->ResourceKey, LOG_UNUSUAL, messageId, MpName, TargetVol); } return mpAllowed; } // IsMountPointAllowed DWORD CheckDependencies( DWORD SrcSignature, DWORD TargetSignature, PBOOL DependencyCorrect ) /*++ Routine Description: Determine if the dependency between the source volume and target volume is set up correctly. Since we are using the cluster APIs, they should insure that the resources are in the same group and have dependencies set. Arguments: SrcSignature - Disk signature of the source volume. TargetSignature - Disk signature of the targe volume. DependencyCorrect - Indicates whether the dependency is set up correctly between the source and target. If set up correctly, this will be returned as TRUE. Return Value: Win32 error code. --*/ { DWORD dwError = NO_ERROR; DEPENDENCY_INFO dependInfo; ZeroMemory( &dependInfo, sizeof(DEPENDENCY_INFO) ); // // We need to find the source's resources first. // dependInfo.SrcSignature = SrcSignature; dependInfo.TargetSignature = TargetSignature; dependInfo.DependencyCorrect = FALSE; // // Worst case assume that the dependency is invalid. // *DependencyCorrect = FALSE; dwError = ResUtilEnumResources( NULL, PHYSICAL_DISK_WSTR, DependencyCallback, &dependInfo ); // // STOP_CLUSTER_ENUMERATIONS is our way to indicate that the // enumerations stopped (possibly early). Check for this return // value and also if the DependencyCorrect flag was set to indicate // status to the caller. // if ( STOP_CLUSTER_ENUMERATIONS == dwError ) { dwError = NO_ERROR; if ( dependInfo.DependencyCorrect ) { *DependencyCorrect = TRUE; } } return dwError; } // CheckDependencies DWORD DependencyCallback( RESOURCE_HANDLE hOriginal, RESOURCE_HANDLE hResource, PVOID lpParams ) /*++ Routine Description: For each enumerated disk resource, get the signature and see if it matches the mount point source signature (passed in the DEPENDENCY_INFO structure). If it does not match, return success so that the disk enumeration continues. If the enumerated resource signature matches the mount point source signature, then check the cluster dependencies and make sure they are correct. Once we have had a match on the signatures, we need to return an error to stop the disk enumeration, so we use STOP_CLUSTER_ENUMERATIONS as that special error value. If the cluster dependencies are acceptable, the DependencyCorrect flag will be set to TRUE in the DEPENDENCY_INFO structure. Arguments: hOriginal - Handle to the original resource. Not used. hResource - Handle to a cluster resource of type PHYSICAL_DISK. lpParams - Pointer to DEPENDENCY_INFO structure. Return Value: STOP_CLUSTER_ENUMERATIONS - Special flag to stop the enumeration process. Win32 error code. --*/ { PDEPENDENCY_INFO dependInfo = lpParams; DWORD dwSignature; DWORD dwError = NO_ERROR; // // Get the disk info and parse the signature from it. // dwError = GetSignatureFromDiskInfo( hResource, &dwSignature ); if ( NO_ERROR != dwError ) { return dwError; } // // Check if we have a resource handle to the source disk or to // a different disk. If the resource is the source disk, // enumerate the dependencies and make sure they are correct. // if ( dwSignature == dependInfo->SrcSignature ) { dwError = EnumSigDependencies( hResource, dependInfo->TargetSignature, &dependInfo->DependencyCorrect ); // // If the dependency check did not get an error, set a fake // error to make the disk enumeration stop. // if ( NO_ERROR == dwError ) { dwError = STOP_CLUSTER_ENUMERATIONS; } } return dwError; } // DependencyCallback DWORD EnumSigDependencies( RESOURCE_HANDLE DependentResource, DWORD DependsOnSignature, PBOOL DependencyCorrect ) /*++ Routine Description: Check that the cluster disk dependencies are correct between the source and target of the mount point. To do this, we open the dependent resource and use the cluster APIs to enumerate all the disk resources dependencies. For each dependency found, check for a match of the DependsOnSignature. If the signatures match, the dependency is correct and we are done. Otherwise, keep checking all the dependencies until we exhaust the list or find a match. Note: Dependency is brought online before the DependentResource. Arguments: DependentResource - Resource Handle to check all the dependencies. DependsOnSignature - Signature of a possibly dependent disk. This disk must be brought online before the DependentResource. DependencyCorrect - Flag set to TRUE when the cluster dependencies between the DependentResource and it's dependency (identified by the DependsOnSignature) are correct. Return Value: Win32 error code. --*/ { HRESENUM resEnum = NULL; HCLUSTER hCluster = NULL; HRESOURCE dependsOnResource = NULL; DWORD idx; DWORD dwError = NO_ERROR; DWORD enumType; DWORD nameLen; DWORD signature; WCHAR enumNameW[MAX_PATH * 2]; __try { hCluster = OpenCluster( NULL ); if ( NULL == hCluster ) { dwError = GetLastError(); __leave; } // // Open an enumerator for iterating through the resources. // resEnum = ClusterResourceOpenEnum( DependentResource, CLUSTER_RESOURCE_ENUM_DEPENDS ); if ( !resEnum ) { dwError = GetLastError(); __leave; } // // Iterate through the dependencies. // idx = 0; while ( TRUE ) { nameLen = MAX_PATH; ZeroMemory( enumNameW, sizeof(enumNameW) ); dwError = ClusterResourceEnum( resEnum, idx, &enumType, enumNameW, &nameLen ); if ( ERROR_NO_MORE_ITEMS == dwError ) { // // The list is exhausted. Indicate no error and leave. This // just means we checked all the dependencies and we didn't find // a match. // dwError = NO_ERROR; __leave; } if ( ERROR_SUCCESS != dwError ) { // // Some type of error, we have to stop processing. // __leave; } // // Now we have the name (in the form of a string) of a resource we are // dependent on. We need to get the signature and compare to the // signature passed in. // dependsOnResource = OpenClusterResource( hCluster, enumNameW ); if ( NULL == dependsOnResource ) { dwError = GetLastError(); __leave; } // // Get the disk signature from the resources disk info. // dwError = GetSignatureFromDiskInfo( dependsOnResource, &signature ); // // If the signature passed in matches the signature we are dependent on, // then the dependency is correct. Otherwise, we have to keep looking. // if ( NO_ERROR == dwError && signature == DependsOnSignature ) { *DependencyCorrect = TRUE; dwError = NO_ERROR; __leave; } // // Look at the next enumeration resource. // CloseClusterResource( dependsOnResource ); dependsOnResource = NULL; idx++; } } __finally { if ( dependsOnResource ) { CloseClusterResource( dependsOnResource ); } if ( resEnum ) { ClusterResourceCloseEnum( resEnum ); } if ( hCluster ) { CloseCluster( hCluster ); } } return dwError; } // EnumSigDependencies DWORD CheckSignatureClustered( DWORD Signature, PBOOL IsClustered ) /*++ Routine Description: Determine if the specified disk signature belongs to a clustered disk. Enumerates the cluster physical disks and tries to find a signature match. The enumeration returns STOP_CLUSTER_ENUMERATIONS when it has found a matching signature. This special error code is to stop the disk enumeration. Arguments: Signature - Disk signature to be checked. IsClustered - Flag indicating disk is clustered. If TRUE, disk is a clustered disk. Return Value: Win32 error code. --*/ { DWORD dwError = NO_ERROR; SIG_INFO sigInfo; ZeroMemory( &sigInfo, sizeof(SIG_INFO) ); sigInfo.Signature = Signature; sigInfo.Clustered = FALSE; *IsClustered = FALSE; dwError = ResUtilEnumResources( NULL, PHYSICAL_DISK_WSTR, SigInfoCallback, &sigInfo ); if ( STOP_CLUSTER_ENUMERATIONS == dwError && sigInfo.Clustered ) { dwError = NO_ERROR; *IsClustered = TRUE; } return dwError; } // CheckSignatureClustered DWORD SigInfoCallback( RESOURCE_HANDLE hOriginal, RESOURCE_HANDLE hResource, PVOID lpParams ) /*++ Routine Description: For each enumerated disk resource, get the signature and see if it matches the specified disk signature (passed in the SIG_INFO structure). If it does not match, return success so that the disk enumeration continues. If the enumerated resource signature matches the mount point source signature, sets the Clustered flag in the SIG_INFO structure to TRUE. Arguments: hOriginal - Handle to the original resource. Not used. hResource - Handle to a cluster resource of type PHYSICAL_DISK. lpParams - Pointer to SIGN_INFO structure. Return Value: STOP_CLUSTER_ENUMERATIONS - Special flag to stop the enumeration process. Win32 error code. --*/ { PSIG_INFO sigInfo = lpParams; DWORD dwSignature; DWORD dwError = NO_ERROR; // // Get the disk info and parse the signature from it. // dwError = GetSignatureFromDiskInfo( hResource, &dwSignature ); if ( NO_ERROR != dwError ) { return dwError; } if ( dwSignature == sigInfo->Signature ) { sigInfo->Clustered = TRUE; // // Return an error to stop the enumeration. // dwError = STOP_CLUSTER_ENUMERATIONS; } return dwError; } // SigInfoCallback DWORD GetSignatureFromDiskInfo( RESOURCE_HANDLE hResource, DWORD *dwSignature ) /*++ Routine Description: Get the signature for the given volume from the cluster using ClusterResourceControl. Arguments: hResource - Handle to a cluster resource of type PHYSICAL_DISK. Signature - On success, the signature is returned into this pointer. Return Value: Win32 error code. --*/ { PBYTE outBuffer = NULL; PCLUSPROP_DISK_SIGNATURE diskSignature; DWORD dwError; DWORD dwBytesReturned; DWORD signature; *dwSignature = 0; dwError = WrapClusterResourceControl( hResource, CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO, &outBuffer, &dwBytesReturned ); if ( NO_ERROR == dwError && outBuffer ) { // // CLUSPROP_DISK_SIGNATURE must always begin the value list returned from the // CLUSCTL code. // diskSignature = (PCLUSPROP_DISK_SIGNATURE)outBuffer; if ( diskSignature->Syntax.dw != CLUSPROP_SYNTAX_DISK_SIGNATURE ) { // // Invalid syntax. // dwError = ERROR_BAD_FORMAT; } else if ( diskSignature->cbLength != sizeof(DWORD) ) { // // Invalid length. // dwError = ERROR_BAD_FORMAT; } else { // // Return the signature. // *dwSignature = diskSignature->dw; } } if (outBuffer) { LocalFree(outBuffer); } return dwError; } // GetSignatureFromDiskInfo DWORD GetSignatureForVolume( PDISK_RESOURCE ResourceEntry, PWSTR MpName, PDWORD Signature ) /*++ Routine Description: Get the signature for the given volume. The signature is found by issuing IOCTL_DISK_GET_DRIVE_LAYOUT_EX or IOCTL_DISK_GET_DRIVE_LAYOUT. The volume must be online for this to work and not reserved by another node. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. MpName - Possible mount point. This will either be a mount point or a drive letter (which is actually a mount point). Format can be: x:\ [Note trailing backslash!] x:\some-mp-name\ [Note trailing backslash!] x:\some-dir\some-mp-name\ [Note trailing backslash!] Signature - On success, the signature is returned into this pointer. Return Value: Win32 error code. --*/ { PDRIVE_LAYOUT_INFORMATION_EX layoutEx = NULL; PDRIVE_LAYOUT_INFORMATION layout = NULL; HANDLE handle = NULL; DWORD bytesReturned; DWORD dwError = NO_ERROR; DWORD lenChars = 0; BOOL restoreStr = FALSE; WCHAR replaceChar; WCHAR drive[ 64 ]; __try { if ( !MpName || !Signature ) { dwError = ERROR_INVALID_PARAMETER; __leave; } *Signature = 0; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"GetSignatureForVolume: Checking mount point (%1!ws!) \n", MpName ); #endif // // If mount point is of form x:\aaa\bbb, temporarily strip off the // string to make it into a drive letter. // lenChars = wcslen( MpName ); if ( lenChars < 3 ) { dwError = ERROR_INVALID_PARAMETER; __leave; } if ( lenChars > 3 ) { restoreStr = TRUE; replaceChar = (WCHAR)*(MpName + CHAR_TO_REPLACE); (WCHAR)*(MpName + CHAR_TO_REPLACE) = 0; } // // Make the name into the form: \\?\x: [Note: no trailing backslash!] // ZeroMemory( drive, sizeof( drive ) ); wcscpy( drive, L"\\\\.\\" ); wcscat( drive, MpName ); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"GetSignatureForVolume: CreateFile using %1!ws! \n", drive ); #endif handle = CreateFileW( drive, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( INVALID_HANDLE_VALUE == handle ) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"GetSignatureForVolume: CreateFile failed, error %1!u! \n", dwError ); __leave; } // // Try IOCTL_DISK_GET_DRIVE_LAYOUT_EX first. If it fails, try with // IOCTL_DISK_GET_DRIVE_LAYOUT. // layoutEx = DoIoctlAndAllocate( handle, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, 0, 0, &bytesReturned ); if ( !layoutEx ) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"GetSignatureForVolume: IOCTL_DISK_GET_DRIVE_LAYOUT_EX failed, error %1!u! \n", dwError ); // // Try the old IOCTL... // layout = DoIoctlAndAllocate( handle, IOCTL_DISK_GET_DRIVE_LAYOUT, 0, 0, &bytesReturned ); if ( !layout ) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"GetSignatureForVolume: IOCTL_DISK_GET_DRIVE_LAYOUT failed, error %1!u! \n", dwError ); // // Fall through and cleanup. // } else { // // Get the signature from the returned structure and return it to // the caller. // *Signature = layout->Signature; // // Have to set NO_ERROR here because the current value shows the // first IOCTL that failed. // dwError = NO_ERROR; } } else { // // Get the signature from the returned structure and return it to // the caller. // if ( PARTITION_STYLE_MBR == layoutEx->PartitionStyle ) { *Signature = layoutEx->Mbr.Signature; } else if ( PARTITION_STYLE_GPT == layoutEx->PartitionStyle ) { // // Since our signatures won't handle the GPT GUID, we have to // simulate a signature. // *Signature = ClusterHashGuid(layoutEx->Gpt.DiskId); } } } __finally { if ( layoutEx ) { free( layoutEx ); } if ( layout ) { free( layout ); } if ( handle ) { CloseHandle( handle ); } // // Restore string to original format. // if ( lenChars > 3 && restoreStr ) { (WCHAR)*(MpName + CHAR_TO_REPLACE) = replaceChar; } } #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"GetSignatureForVolume: Returning signature %1!x! for mount point (%2!ws!) \n", *Signature, MpName ); #endif return dwError; } // GetSignatureForVolume BOOL GetOffsetFromPartNo( DWORD PartitionNo, PMOUNTIE_INFO Info, PLARGE_INTEGER Offset ) /*++ Routine Description: Given the partition number and the drive layout, return the byte offset for the specified partition. Arguments: PartitionNo - Supplies the partition number. Zero is invalid since partition zero represents the entire disk. Info - Pointer to MOUNTIE_INFO based on drive layout information. Offset - Pointer to hold the returned byte offset for the partition. Space is allocated by the caller. Return Value: TRUE if successful. --*/ { PMOUNTIE_PARTITION entry; DWORD idx; DWORD partitionCount; BOOL retVal = FALSE; if ( !PartitionNo || !Info || !Offset ) { goto FnExit; } if ( 0 == Info->Volume ) { goto FnExit; } #if DBG (DiskpLogEvent)( NULL, // ResourceEntry->ResourceHandle, LOG_INFORMATION, L"GetOffsetFromPartNo: partition %1!u! \n", PartitionNo ); #endif Offset->QuadPart = 0; // Offset of zero is invalid. This will indicate an error. partitionCount = Info->Volume->PartitionCount; entry = Info->Volume->Partition; for ( idx = 0; idx < partitionCount; ++idx, ++entry) { #if DBG (DiskpLogEvent)( NULL, // ResourceEntry->ResourceHandle, LOG_INFORMATION, L"GetOffsetFromPartNo: index %1!u! offset %2!x! \n", idx, entry->StartingOffset.LowPart ); #endif if ( entry->PartitionNumber == PartitionNo ) { Offset->QuadPart = entry->StartingOffset.QuadPart; retVal = TRUE; break; } } FnExit: return retVal; } // GetOffsetFromPartNo BOOL GetPartNoFromOffset( PLARGE_INTEGER Offset, PMOUNTIE_INFO Info, PDWORD PartitionNumber ) /*++ Routine Description: Given the offset and the drive layout, return the partition number for the specified offset. Arguments: Offset - Pointer to the byte offset. Info - Pointer to MOUNTIE_INFO based on drive layout information. PartitionNo - Pointer to hold the returned partition number. Space is allocated by the caller. Return Value: TRUE if successful. --*/ { PMOUNTIE_PARTITION entry; DWORD idx; DWORD partitionCount; BOOL retVal = FALSE; if ( !Offset->QuadPart || !Info || !PartitionNumber) { goto FnExit; } if ( 0 == Info->Volume ) { goto FnExit; } *PartitionNumber = 0; // Partition zero is invalid. This will indicate an error. partitionCount = Info->Volume->PartitionCount; entry = Info->Volume->Partition; for ( idx = 0; idx < partitionCount; ++idx, ++entry ) { if ( entry->StartingOffset.QuadPart == Offset->QuadPart ) { *PartitionNumber = entry->PartitionNumber; retVal = TRUE; break; } } FnExit: return retVal; } // GetPartNoFromOffset VOID PrintStrList( PDISK_RESOURCE ResourceEntry, LPWSTR MultiSzList, DWORD ListBytes ) /*++ Routine Description: Display the list in the cluster log. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. MultiSzList - REG_MULTI_SZ string ListBytes - Number of bytes in MultiSzList, not number of WCHARs! Return Value: None. --*/ { PWSTR currentStr; PWCHAR data; LARGE_INTEGER offset; DWORD currentStrLenChars = 0; DWORD count; if ( !ResourceEntry || !MultiSzList || 0 == ListBytes ) { return; } (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L" Offset String \n" ); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"================ ====================================== \n" ); EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); currentStr = (PWCHAR)MultiSzList; currentStrLenChars = wcslen( currentStr ); while ( currentStrLenChars ) { data = NULL; offset.QuadPart = 0; // // Convert the offset from a string to a large integer value. // count = swscanf( currentStr, L"%I64x ", &offset.QuadPart ); if ( 0 == count ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"Error: Unable to parse offset from currentStr (%1!ws!) \n", currentStr ); // Stop processing the list... break; } // // Data starts just after the first space. // data = wcschr( currentStr, SPACE_CHAR ); if ( !data || wcslen(data) < 3 ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"Error: Unable to get mount point str from currentStr %1!ws! \n", currentStr ); // Stop processing the list... break; } // // Skip past the space character. Note that the length was previously validated. // if ( SPACE_CHAR == *data ) { data++; } (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"%1!08X!%2!08X! %3!ws! \n", // couldn't get !I64X! to work... offset.HighPart, offset.LowPart, data ); currentStr += currentStrLenChars + 1; currentStrLenChars = wcslen( currentStr ); } LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"*** End of list *** \n" ); } // PrintStrList static DWORD SetMPListThread( LPVOID lpThreadParameter ) /*++ Routine Description: Mount point list update thread. Updates the cluster data base. Arguments: lpThreadParameter - stores ResourceEntry. Return Value: None --*/ { DWORD dwError; PDISK_RESOURCE ResourceEntry = lpThreadParameter; DWORD idx; (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: started.\n"); // // Will die in 10 minutes if unsuccessful // for ( idx = 0; idx < 300; ++idx ) { // // Wait for either the terminate event or the timeout // dwError = WaitForSingleObject( DisksTerminateEvent, 2000 ); if ( WAIT_TIMEOUT == dwError ) { EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); #if DBG DumpDiskInfoParams( ResourceEntry ); #endif // // Bug in ResUtilSetPropertyParameterBlock. It will update the cluster // database with updated MULTI_SZ data, but it doesn't clear the values // appropriately. Use ClusterRegDeleteValue to make sure the lists are // cleared if they have been deleted. // if ( !ResourceEntry->DiskInfo.Params.MPVolGuids && 0 == ResourceEntry->DiskInfo.Params.MPVolGuidsSize ) { dwError = ClusterRegDeleteValue( ResourceEntry->ResourceParametersKey, CLUSREG_NAME_PHYSDISK_MPVOLGUIDS ); } // // Timer expired. Update the cluster database. // dwError = ResUtilSetPropertyParameterBlock( ResourceEntry->ResourceParametersKey, DiskResourcePrivateProperties, NULL, (LPBYTE) &ResourceEntry->DiskInfo.Params, NULL, 0, NULL ); LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); if ( ERROR_SUCCESS == dwError ) { // // We're done. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: mount point info updated in cluster data base \n" ); break; } else if ( ERROR_SHARING_PAUSED != dwError ) { // // If the drive is not yet online, we should have seen ERROR_SHARING_PAUSED. If // we see any other error, something bad happened. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"SetMPListThread: Failed to update cluster data base, error = %1!u! \n", dwError ); break; } (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: Wait again for event or timeout, count %1!u! \n", idx ); } else { // // The terminate event is possibly set. // (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"SetMPListThread: WaitForSingleObject returned error = %1!u! \n", dwError ); break; } } // // Thread ending, clear the flag. // InterlockedExchange( &ResourceEntry->MPInfo.MPUpdateThreadIsActive, 0 ); return(ERROR_SUCCESS); } // SetMPListThread DWORD PostMPInfoIntoRegistry( PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Set the DiskResourcePrivateProperties in the cluster database. If the disk is not yet online, create a thread to update the cluster database. The disk might not be fully online if we are in the process of bringing the quorum disk online and trying to update the mount point information. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: Win32 error code. --*/ { DWORD dwError; // // Update the cluster database. // EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); #if DBG DumpDiskInfoParams( ResourceEntry ); #endif // // Bug in ResUtilSetPropertyParameterBlock. It will update the cluster // database with updated MULTI_SZ data, but it doesn't clear the values // appropriately. Use ClusterRegDeleteValue to make sure the lists are // cleared if they have been deleted. // if ( !ResourceEntry->DiskInfo.Params.MPVolGuids && 0 == ResourceEntry->DiskInfo.Params.MPVolGuidsSize ) { dwError = ClusterRegDeleteValue( ResourceEntry->ResourceParametersKey, CLUSREG_NAME_PHYSDISK_MPVOLGUIDS ); } dwError = ResUtilSetPropertyParameterBlock( ResourceEntry->ResourceParametersKey, DiskResourcePrivateProperties, NULL, (LPBYTE) &ResourceEntry->DiskInfo.Params, NULL, 0, NULL ); LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); // // If the update failed and the disk is not yet online, it will fail with // ERROR_SHARING_PAUSED. In this case, create a thread to update the cluster // data base. Any other error or success should simply continue. // if ( ERROR_SHARING_PAUSED == dwError ) { // // Check if the thread is already active. If it is, exit with an error. // if ( InterlockedCompareExchange( &ResourceEntry->MPInfo.MPUpdateThreadIsActive, 1, 0 ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"PostMPInfoIntoRegistry: MountPoint thread is already running \n" ); dwError = ERROR_ALREADY_EXISTS; } else { HANDLE thread; DWORD threadId; thread = CreateThread( NULL, 0, SetMPListThread, ResourceEntry, 0, &threadId ); if ( NULL == thread ) { // // Thread creation failed. Log error, clear thread active flag, // and return. // dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"PostMPInfoIntoRegistry: CreateThread failed, error %1!u!\n", dwError ); InterlockedExchange( &ResourceEntry->MPInfo.MPUpdateThreadIsActive, 0 ); } else { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"PostMPInfoIntoRegistry: Thread created \n" ); // // Thread created. Indicate no error. // CloseHandle( thread ); dwError = ERROR_SUCCESS; } } } else { (DiskpLogEvent)( ResourceEntry->ResourceHandle, ( NO_ERROR == dwError ? LOG_INFORMATION : LOG_ERROR ), L"PostMPInfoIntoRegistry: ResUtilSetPropertyParameterBlock returned %1!u! \n", dwError ); } return dwError; } // PostMpInfoIntoRegistry DWORD WrapClusterResourceControl( RESOURCE_HANDLE hResource, DWORD dwControlCode, LPVOID *OutBuffer, DWORD *dwBytesReturned ) /*++ Routine Description: Wrap the call to ClusterResourceControl. This routine will allocate the output buffer, call ClusterResourceControl, and return the address of the output buffer to the caller. The caller of this routine is responsible for freeing the output buffer. Arguments: hResource - Handle to a cluster resource of type PHYSICAL_DISK. dwControlCode - The resource control code to be performed. OutBuffer - Address to store the output buffer. dwBytesReturned - Number of bytes in the allocated output buffer. Return Value: Win32 error code. --*/ { DWORD dwError; DWORD cbOutBufferSize = MAX_PATH; DWORD cbResultSize = MAX_PATH; LPVOID tempOutBuffer = LocalAlloc( LPTR, cbOutBufferSize ); dwError = ClusterResourceControl( hResource, NULL, dwControlCode, NULL, 0, tempOutBuffer, cbOutBufferSize, &cbResultSize ); // // Reallocation routine if buffer is too small // if ( ERROR_MORE_DATA == dwError ) { LocalFree( tempOutBuffer ); cbOutBufferSize = cbResultSize; tempOutBuffer = LocalAlloc( LPTR, cbOutBufferSize ); dwError = ClusterResourceControl( hResource, NULL, dwControlCode, NULL, 0, tempOutBuffer, cbOutBufferSize, &cbResultSize ); } // // On success, give the user the allocated buffer. The user is responsible // for freeing this buffer. On failure, free the buffer and return a status. // if ( NO_ERROR == dwError ) { *OutBuffer = tempOutBuffer; *dwBytesReturned = cbResultSize; } else { *OutBuffer = NULL; *dwBytesReturned = 0; LocalFree( tempOutBuffer ); } return dwError; } // WrapClusterResourceControl VOID DisksMountPointCleanup( PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Cleanup everything the mount point code used. This routine should be called in DisksClose. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: None. --*/ { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"DisksMountPointCleanup: Cleanup mount point information \n" ); // // If existing MPVolGuids list, free it. // if ( ResourceEntry->DiskInfo.Params.MPVolGuids ) { LocalFree( ResourceEntry->DiskInfo.Params.MPVolGuids ); ResourceEntry->DiskInfo.Params.MPVolGuidsSize = 0; ResourceEntry->DiskInfo.Params.MPVolGuids = NULL; } ResourceEntry->MPInfo.Initialized = FALSE; DeleteCriticalSection( &ResourceEntry->MPInfo.MPLock ); } // DisksMountPointCleanup VOID DisksMountPointInitialize( PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Prepare the mount point structures in the ResourceEntry for use. This routine should be called in DisksOpen. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: None. --*/ { InitializeCriticalSection( &ResourceEntry->MPInfo.MPLock ); ResourceEntry->MPInfo.Initialized = TRUE; InterlockedExchange( &ResourceEntry->MPInfo.MPUpdateThreadIsActive, 0 ); InterlockedExchange( &ResourceEntry->MPInfo.MPListCreateInProcess, 0 ); } // DisksMountPointInitialize DWORD DisksUpdateMPList( PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Validate the mount points. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: Win32 error code. --*/ { DWORD dwError = NO_ERROR; (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"DisksUpdateMPList: Processing PNP mountpoint notification \n" ); // // Check if the MPList is in process of being updated. If it is, exit with an error. // if ( InterlockedCompareExchange( &ResourceEntry->MPInfo.MPListCreateInProcess, 1, 0 ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"DisksUpdateMPList: Update in process, bypassing PNP notification \n" ); return ERROR_BUSY; } dwError = ValidateMountPoints( ResourceEntry ); InterlockedExchange( &ResourceEntry->MPInfo.MPListCreateInProcess, 0 ); return dwError; } // DisksUpdateMPList DWORD DisksProcessMPControlCode( PDISK_RESOURCE ResourceEntry, DWORD ControlCode ) /*++ Routine Description: Process the disk mount point control code. Since we are in the thread that handed us the control code (DisksResourceControl), we can't do much except a separate thread to do the bulk of the mount point processing. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. ControlCode - Cluster resource control for mount point processing. Return Value: --*/ { HANDLE thread; DWORD threadId; DWORD dwError = NO_ERROR; __try { // // Create a thread to update the mount point list. We don't need to // copy the ResourceEntry as this pointer will be valid when the thread // runs. // thread = CreateThread( NULL, 0, DisksUpdateMPList, ResourceEntry, 0, &threadId ); if ( NULL == thread ) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"DisksProcessMPControlCode: CreateThread failed %1!u! \n", dwError ); __leave; } // // Thread created. Indicate no error. // CloseHandle( thread ); dwError = NO_ERROR; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"DisksProcessMPControlCode: Created thread to process control code \n" ); #endif } __finally { } return dwError; } // DisksProcessMPControlCode DWORD ValidateMountPoints( IN OUT PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: For each partition on this disk, get the mountpoints directed toward this partition. Check each mountpoint to make sure it is allowed. For those mountpoints not allowed, write a message to system event log indicating why it is an invalid mountpoint. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: ERROR_INVALID_DATA - Partition info stored in MountieInfo is invalid. Win32 error code. --*/ { PMOUNTIE_PARTITION entry; DWORD dwError = ERROR_SUCCESS; DWORD nPartitions = MountiePartitionCount( &ResourceEntry->MountieInfo ); DWORD physicalDrive = ResourceEntry->DiskInfo.PhysicalDrive; DWORD idx; WCHAR szGlobalDiskPartName[MAX_PATH]; WCHAR szVolumeName[MAX_PATH]; ZeroMemory( szGlobalDiskPartName, sizeof(szGlobalDiskPartName) ); ZeroMemory( szVolumeName, sizeof(szVolumeName) ); // // Check each interesting partition. Since only "valid" partitions are // saved in the MountieInfo structure, we will only look at those (ignoring those // partitions that are not NTFS). // for ( idx = 0; idx < nPartitions; ++idx ) { entry = MountiePartition( &ResourceEntry->MountieInfo, idx ); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"ValidateMountPoints: index %1!u! entry %2!x! \n", idx, entry ); #endif if ( !entry ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"ValidateMountPoints: no partition entry for index %1!u! \n", idx ); // // Something bad happened to our data structures. // dwError = ERROR_INVALID_DATA; break; } // // Create the device name of the form: // \\?\GLOBALROOT\Device\HarddiskX\PartitionY\ (uses trailing backslash) // _snwprintf( szGlobalDiskPartName, MAX_PATH, // Number of CHARS, not bytes GLOBALROOT_HARDDISK_PARTITION_FMT, physicalDrive, entry->PartitionNumber ); #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"ValidateMountPoints: Using name (%1!ws!) \n", szGlobalDiskPartName ); #endif if ( !GetVolumeNameForVolumeMountPointW( szGlobalDiskPartName, szVolumeName, sizeof(szVolumeName)/sizeof(WCHAR) )) { dwError = GetLastError(); (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"ValidateMountPoints: GetVolumeNameForVolumeMountPoint for (%1!ws!) returned %2!u!\n", szGlobalDiskPartName, dwError ); break; } #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"ValidateMountPoints: Returned volume name (%1!ws!) \n", szVolumeName ); #endif CheckMPsForVolume( ResourceEntry, szVolumeName ); } return dwError; } // ValidateMountPoints VOID CheckMPsForVolume( IN OUT PDISK_RESOURCE ResourceEntry, IN PWSTR VolumeName ) /*++ Routine Description: For the specified volume, find all mount points directed towards this volume. For each mountpoint, make sure it is allowed. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. VolumeName - Target volume for the mount point. Format is: \\?\Volume{GUID}\ [Note trailing backslash!] Return Value: None. --*/ { PWSTR volumePaths = NULL; PWSTR currentMP; DWORD dwError; __try { // // GetMountPoints will allocate a MultiSz buffer with // all the mount points for this target volume. // dwError = GetMountPoints( VolumeName, &volumePaths ); if ( NO_ERROR != dwError || !volumePaths ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"CheckMPsForVolume: GetMountPoints returns %1!u! \n", dwError ); __leave; } // // Loop through each mount point in the list. // // Each mount point will either be a mount point or a drive letter // (which is actually a mount point). Format can be: // // x:\ [Note trailing backslash!] // x:\some-mp-name\ [Note trailing backslash!] // x:\some-dir\some-mp-name\ [Note trailing backslash!] // currentMP = volumePaths; for (;;) { IsMountPointAllowed( currentMP, VolumeName, ResourceEntry ); // // Skip through current mount point to end of string. // while (*currentMP++); // // If next mount point is empty, the list is exhausted. // if (!*currentMP) { break; } } } __finally { if ( volumePaths ) { LocalFree( volumePaths ); } } } // CheckMPsForVolume DWORD GetMountPoints( PWSTR VolumeName, PWSTR *VolumePaths ) /*++ Routine Description: For the specified volume, find all mount points directed towards this volume. The mount point buffer will be allocated by this routine and must be freed by the caller. Arguments: VolumeName - Target volume for the mount point. Format is: \\?\Volume{GUID}\ [Note trailing backslash!] VolumePaths - Pointer to a MultiSz string containing all mount points directed toward this volume. If there are no mount points, this pointer will be set to NULL. The caller is responsible for freeing this buffer. Return Value: Win32 error code. --*/ { DWORD lenChars; WCHAR messageBuffer[MAX_PATH]; PWSTR paths = NULL; PWSTR currentMP; DWORD dwError; if ( !VolumeName || !VolumePaths ) { return ERROR_INVALID_PARAMETER; } *VolumePaths = NULL; // // Determine the size of the buffer we need. // if ( !GetVolumePathNamesForVolumeName( VolumeName, NULL, 0, &lenChars ) ) { dwError = GetLastError(); if ( ERROR_MORE_DATA != dwError ) { return dwError; } } // // Allocate the mount point buffer. // paths = LocalAlloc( 0, lenChars * sizeof(WCHAR) ); if ( !paths ) { dwError = GetLastError(); return dwError; } // // Get the mount points. // if ( !GetVolumePathNamesForVolumeName( VolumeName, paths, lenChars, NULL ) ) { dwError = GetLastError(); LocalFree( paths ); return dwError; } // // If no mount points, free the buffer and return to the caller. // if ( !paths[0] ) { LocalFree(paths); // // If no mount points for this volume, return no error and a NULL // pointer to the mount point list. // return NO_ERROR; } *VolumePaths = paths; return NO_ERROR; } // GetMountPoints DWORD ValidateListOffsets( IN OUT PDISK_RESOURCE ResourceEntry, IN PWSTR MasterList ) /*++ Routine Description: Verify each entry in the list to make sure the byte offset is valid. Also, count the number of entries to make sure there are not too many entries saved (there should be one VolGuid per node times the number of volumes on the disk). Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. MasterList - REG_MULTI_SZ list to be checked. Return Value: ERROR_INVALID_DATA - List contains at least one invalid byte offset value, possibly more. ERROR_INSUFFICIENT_BUFFER - List possibly corrupt as it contains too many entries. Win32 error code. --*/ { PWCHAR currentStr; DWORD currentStrLenChars = 0; DWORD dwError = NO_ERROR; DWORD partitionNo; DWORD numberOfEntries = 0; DWORD count; LARGE_INTEGER offset; BOOL invalidOffset = FALSE; EnterCriticalSection( &ResourceEntry->MPInfo.MPLock ); // // Parse through the list. // for ( currentStr = (PWCHAR)MasterList, currentStrLenChars = wcslen( currentStr ) ; currentStrLenChars ; currentStr += currentStrLenChars + 1, currentStrLenChars = wcslen( currentStr ) ) { offset.QuadPart = 0; #if DBG (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"ValidateListOffsets: CurrentStr (%1!ws!), numChars %2!u! \n", currentStr, currentStrLenChars ); #endif // // Convert the offset from a string to a large integer value. // count = swscanf( currentStr, L"%I64x ", &offset.QuadPart ); if ( 0 == count ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"ValidateListOffsets: Unable to parse offset from currentStr (%1!ws!) \n", currentStr ); numberOfEntries++; continue; } // // Convert the offset to a partition number. // if ( !GetPartNoFromOffset( &offset, &ResourceEntry->MountieInfo, &partitionNo ) ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_ERROR, L"ValidateListOffsets: Unable to convert offset ( %1!08X!%2!08X! ) to partition number \n", offset.HighPart, offset.LowPart ); // couldn't get !I64X! to work... invalidOffset = TRUE; // As soon as we find an invalid partition number, we are done. break; } numberOfEntries++; } if ( invalidOffset ) { dwError = ERROR_INVALID_DATA; } else if ( numberOfEntries > MAX_ALLOWED_VOLGUID_ENTRIES_PER_DISK ) { (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_WARNING, L"ValidateListOffset: VolGuid list too large, %1!u! entries \n", numberOfEntries ); #if USEMOUNTPOINTS_KEY // // See if the user wants to ignore the number of entries in VolGuid list. // if ( !(ResourceEntry->DiskInfo.Params.UseMountPoints & MPS_IGNORE_MAX_VOLGUIDS) ) { // // Log an error to system event log. // ClusResLogSystemEventByKey(ResourceEntry->ResourceKey, LOG_UNUSUAL, RES_DISK_MP_VOLGUID_LIST_EXCESSIVE ); dwError = ERROR_INSUFFICIENT_BUFFER; } #endif } LeaveCriticalSection( &ResourceEntry->MPInfo.MPLock ); return dwError; } // ValidateListOffsets BOOL MPIsDriveLetter( IN PWSTR MountPoint ) /*++ Routine Description: Determine if the mount point string is a drive letter. A drive letter will be represented by a string of the form "x:\" with a length of 3. Arguments: MountPoint - Mount point string to be verified. Return Value: TRUE if the mount point string represents a drive letter. --*/ { DWORD lenChars; lenChars = wcslen( MountPoint ); if ( 3 == lenChars && L':' == MountPoint[1] && L'\\' == MountPoint[2] && iswalpha( MountPoint[0] ) ) { return TRUE; } return FALSE; } // MPIsDriveLetter #if DBG // // Debug helper routine // VOID DumpDiskInfoParams( PDISK_RESOURCE ResourceEntry ) /*++ Routine Description: Display in the cluster log interesting mountpoint information. Arguments: ResourceEntry - Pointer to the DISK_RESOURCE structure. Return Value: None. --*/ { #if 0 // Drive is not currently stored (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: Signature %1!x! Drive (%2!ws!) \n", ResourceEntry->DiskInfo.Params.Signature, ResourceEntry->DiskInfo.Params.Drive ); #endif (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: Signature %1!x! \n", ResourceEntry->DiskInfo.Params.Signature ); #if USEMOUNTPOINTS_KEY (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: SkipChkdsk %1!x! ConditionalMount %2!x! UseMountPoints %3!x! \n", ResourceEntry->DiskInfo.Params.SkipChkdsk, ResourceEntry->DiskInfo.Params.ConditionalMount, ResourceEntry->DiskInfo.Params.UseMountPoints ); #else (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: SkipChkdsk %1!x! ConditionalMount %2!x! \n", ResourceEntry->DiskInfo.Params.SkipChkdsk, ResourceEntry->DiskInfo.Params.ConditionalMount ); #endif (DiskpLogEvent)( ResourceEntry->ResourceHandle, LOG_INFORMATION, L"SetMPListThread: VolGuid list %1!x! VolGuid size %2!u! \n", ResourceEntry->DiskInfo.Params.MPVolGuids, ResourceEntry->DiskInfo.Params.MPVolGuidsSize ); } // DumpDiskInfoParams #endif