/*++ Copyright (c) 2002 Microsoft Corporation Abstract: @doc @module cluster.cxx | Implementation of CVssClusterAPI @end Author: Adi Oltean [aoltean] 04/25/2002 Revision History: Name Date Comments aoltean 03/14/2001 Created --*/ ///////////////////////////////////////////////////////////////////////////// // Includes #include "stdafx.hxx" #include #pragma warning(disable: 4554) #include #include #include "vs_inc.hxx" #include "vs_reg.hxx" #include "vs_clus.hxx" #include "vs_quorum.hxx" //////////////////////////////////////////////////////////////////////// // Standard foo for file name aliasing. This code block must be after // all includes of VSS header files. // #ifdef VSS_FILE_ALIAS #undef VSS_FILE_ALIAS #endif #define VSS_FILE_ALIAS "CLUCLUSC" // //////////////////////////////////////////////////////////////////////// #define CHECK_COM( Call ) \ { \ ft.hr = Call; \ if (ft.HrFailed()) \ ft.TranslateComError(VSSDBG_GEN, VSS_WSTRINGIZE(Call)); \ } ///////////////////////////////////////////////////////////////////////////// // Constants const WCHAR x_ClusReg_TypeName_PhysicalDisk[] = L"Physical Disk"; const WCHAR x_ClusReg_TypeName_TaskScheduler[] = L"Volume Shadow Copy Service Task"; const WCHAR x_ClusReg_Name_PhysicalDisk_MPVolGuids[] = L"MPVolGuids"; const WCHAR x_ClusReg_Name_TaskScheduler_ApplicationName[] = L"ApplicationName"; const WCHAR x_ClusReg_Name_TaskScheduler_ApplicationParams[] = L"ApplicationParams"; const WCHAR x_ClusReg_Name_TaskScheduler_TriggerArray[] = L"TriggerArray"; // // BUG# 698766 - Deployment blocker: // Failure on managing diff areas on large volumes since Cluster Timeout is too low // // 600 seconds (10 min) timeout in bringing the resource online/ofline // const x_nDefaultBringOnlineTimeout = 600; const x_nDefaultBringOfflineTimeout = 600; ///////////////////////////////////////////////////////////////////////////// // CVssClusterResourceList definition and implementation class CVssClusterResourceList { public: // Pushes a resource in the list // Increases the ref count by 1 void Push(ISClusResource* pResource) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterResourceList::Push" ); try { // AddRef CComPtr ptrResource = pResource; // AddRef resourceList.push(ptrResource); // Release (CComPtr destructor) } catch(const std::bad_alloc&) { ft.ThrowOutOfMemory(VSSDBG_GEN); } catch(const std::exception& ex) { ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"STL exception catched: %hs", ex.what()); } } // Pops a resource from the list // Does not decrease the ref count by 1 (that is caller responsibility - actually the smart pointer will do it) void Pop(CComPtr & pResource) { pResource = NULL; // If the list is empty, stop here if (resourceList.empty()) return; // AddRef pResource = resourceList.front(); // Release resourceList.pop(); } private: std::queue< CComPtr > resourceList; }; ///////////////////////////////////////////////////////////////////////////// // CVssClusterAPI implementation // Default constructor CVssClusterAPI::CVssClusterAPI(): m_dwOfflineTimeout(x_nDefaultBringOfflineTimeout), m_dwOnlineTimeout(x_nDefaultBringOnlineTimeout) { } // Returns FALSE if the cluster API is not present bool CVssClusterAPI::Initialize( IN LPCWSTR pwszCluster /* = NULL */ ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::Initialize" ); // Get the offline and online values from registry (if any) try { CVssRegistryKey keyVssSettings(KEY_READ); if (keyVssSettings.Open(HKEY_LOCAL_MACHINE, x_wszVssCASettingsPath)) { // Reading the online timeout DWORD dwRegistryOnlineTimeout = 0; if (keyVssSettings.GetValue(x_wszVssOnlineTimeoutValueName, dwRegistryOnlineTimeout, false)) m_dwOnlineTimeout = dwRegistryOnlineTimeout; // Reading the offline timeout DWORD dwRegistryOfflineTimeout = 0; if (keyVssSettings.GetValue(x_wszVssOfflineTimeoutValueName, dwRegistryOfflineTimeout, false)) m_dwOfflineTimeout = dwRegistryOfflineTimeout; } } VSS_STANDARD_CATCH(ft) // Ignore any exceptions... // Get the cluster object ft.hr = m_pCluster.CoCreateInstance( _uuidof(Cluster)); if (ft.HrFailed()) { ft.Trace(VSSDBG_GEN, L"m_pCluster.CoCreateInstance( _uuidof(Cluster)) [0x%08lx]", ft.hr); return ft.Exit(false); } // Open the local cluster ft.hr = m_pCluster->Open(CVssComBSTR(pwszCluster? pwszCluster: L"")); if (ft.HrFailed()) { ft.Trace(VSSDBG_GEN, L"m_pCluster->Open(%s) [0x%08lx]", pwszCluster, ft.hr); return ft.Exit(false); } return ft.Exit(true); } // Make pwszFromVolumeName to be dependent on pwszToVolumeName // - Returns TRUE if the dependency was added // - Returns FALSE if a dependency does not need to be added // - Throws E_INVALIDARG on bad parameters // - Throws VSS_E_VOLUME_NOT_SUPPORTED when attempting to add a bogus dependency bool CVssClusterAPI::AddDependency( IN LPCWSTR pwszFromVolumeName, IN LPCWSTR pwszToVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::AddDependency" ); ft.Trace( VSSDBG_GEN, L"Parameters: From = %s, To = %s", pwszFromVolumeName, pwszToVolumeName); if ((pwszFromVolumeName == NULL) || (pwszFromVolumeName == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } CComPtr pFromResource = GetPhysicalDiskResourceForVolumeName(pwszFromVolumeName); CComPtr pToResource = GetPhysicalDiskResourceForVolumeName(pwszToVolumeName); // Ignore the case if no volumes is shared if ((pFromResource == NULL) && (pToResource == NULL)) { ft.Trace(VSSDBG_GEN, L"Original and diff area volumes are no shared. No dependency cannot be added"); return ft.Exit(false); } // Either both volumes must belong to resources or none of them if ((pFromResource == NULL) && (pToResource != NULL)) ft.Throw(VSSDBG_GEN, VSS_E_VOLUME_NOT_SUPPORTED, L"Cannot create a dependency from a non-shared volume to a shared diff area"); if ((pFromResource != NULL) && (pToResource == NULL)) ft.Throw(VSSDBG_GEN, VSS_E_VOLUME_NOT_SUPPORTED, L"Cannot create a dependency from a shared volume to a non-shared diff area"); BS_ASSERT((pFromResource != NULL) && (pToResource != NULL)); // Ignore if both belong to the same physical disk if (AreResourcesEqual(pFromResource, pToResource)) { if (wcscmp(pwszFromVolumeName, pwszToVolumeName) == 0) { ft.Trace(VSSDBG_GEN, L"Volumes are identical. No dependency is added"); return ft.Exit(false); } else ft.Throw(VSSDBG_GEN, VSS_E_VOLUME_NOT_SUPPORTED, L"Cannot create a dependency between different volumes on the same disk resource"); } // Now check if the dependency already exists if (IsDependencyAlreadyEstablished(pFromResource, pToResource)) { ft.Trace(VSSDBG_GEN, L"Dependency already exists."); return ft.Exit(false); } // Now check if we can establish a dependency (for ex. the resources are part of the same group) if (!CanEstablishDependency(pFromResource, pToResource)) ft.Throw(VSSDBG_GEN, VSS_E_VOLUME_NOT_SUPPORTED, L"Cannot create a dependency between volumes"); // Get the dependencies collection for the "FROM" resource CComPtr pDependencies; CHECK_COM( pFromResource->get_Dependencies(&pDependencies) ); // Get the list of resources that would need to be brought online after this resource is brought online CVssClusterResourceList finalList; GetFinalOnlineResourceList(pFromResource, finalList); // Take the resource offline TakeResourceOffline(pFromResource); // Add the dependency ft.hr = pDependencies->AddItem(pToResource); // Bring all the affected resources back online BringResourceListOnline(finalList); // Rethrow if needed if (ft.HrFailed()) ft.ReThrow(); return ft.Exit(true); } // Remove the dependency // - Returns TRUE if the dependency was removed // - Returns FALSE if the dependency could not be found // - Throws E_INVALIDARG on bad parameters bool CVssClusterAPI::RemoveDependency( IN LPCWSTR pwszFromVolumeName, IN LPCWSTR pwszToVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::RemoveDependency" ); ft.Trace( VSSDBG_GEN, L"Parameters: From = %s, To = %s", pwszFromVolumeName, pwszToVolumeName); if ((pwszFromVolumeName == NULL) || (pwszFromVolumeName == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the corresponding resources CComPtr pFromResource = GetPhysicalDiskResourceForVolumeName(pwszFromVolumeName); CComPtr pToResource = GetPhysicalDiskResourceForVolumeName(pwszToVolumeName); // If both volumes are not belonging to any resource, then ignore them if ((pFromResource == NULL) || (pToResource == NULL)) { ft.Trace(VSSDBG_GEN, L"One of the volumes does not belong to a physical disk resource."); return ft.Exit(false); } if (AreResourcesEqual(pFromResource, pToResource)) { ft.Trace(VSSDBG_GEN, L"Volumes belong to the same resource. No dependency is removed"); return ft.Exit(false); } // Get the dependents collection for the "FROM" resource CComPtr pDependencies; CHECK_COM( pFromResource->get_Dependencies(&pDependencies) ); // Get the dependencies count LONG lDependenciesCount = 0; CHECK_COM( pDependencies->get_Count(&lDependenciesCount) ); // Iterate through all dependencies for (INT nDependencyIndex = 1; nDependencyIndex <= lDependenciesCount; nDependencyIndex++) { // Get the dependency with that index CComPtr pDependencyResource; CComVariant varDependencyIndex = nDependencyIndex; CHECK_COM( pDependencies->get_Item(varDependencyIndex, &pDependencyResource) ); // Check to see if this is our resource. If not, continue if (!AreResourcesEqual(pToResource, pDependencyResource)) continue; // Get the list of resources that would need to be brought online after this resource is brought online CVssClusterResourceList finalList; GetFinalOnlineResourceList(pFromResource, finalList); // Take the resource offline TakeResourceOffline(pFromResource); // Remove the association ft.hr = pDependencies->RemoveItem(varDependencyIndex); // Bring all the affected resources back online BringResourceListOnline(finalList); // Rethrow if needed if (ft.HrFailed()) ft.ReThrow(); return ft.Exit(true); } return ft.Exit(false); } // Adds the registry key to the cluster resource that corresponds to our disk. // - Returns TRUE if the reg key was added // - Returns FALSE if there is no need to add a reg key // - Throws E_INVALIDARG on bad parameters // NOTE: this shouldn't fail if the checkpoint is already added! bool CVssClusterAPI::AddRegistryKey( IN LPCWSTR pwszVolumeName, IN LPCWSTR pwszPathFormat, IN ... ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::AddRegistryKey" ); ft.Trace( VSSDBG_GEN, L"Parameters: From = %s, Reg = %s", pwszVolumeName, pwszPathFormat); if ((pwszVolumeName == NULL) || (pwszPathFormat == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Build the path to the key WCHAR wszKeyPath[x_nVssMaxRegBuffer]; va_list marker; va_start( marker, pwszPathFormat ); ft.hr = StringCchVPrintfW( STRING_CCH_PARAM(wszKeyPath), pwszPathFormat, marker ); va_end( marker ); if (ft.HrFailed()) ft.TranslateGenericError(VSSDBG_GEN, ft.hr, L"StringCchVPrintfW()"); ft.Trace( VSSDBG_GEN, L"Formatted path: '%s'", wszKeyPath); // Build the BSTR CVssComBSTR bstrPath = wszKeyPath; // Get the corresponding resource CComPtr pResource = GetPhysicalDiskResourceForVolumeName(pwszVolumeName); if (pResource == NULL) { ft.Trace(VSSDBG_GEN, L"The volume is not shared. No registry key can be added"); return ft.Exit(false); } // Get the Registry checkpoints collection CComPtr pRegKeys; CHECK_COM( pResource->get_RegistryKeys(&pRegKeys) ); // Enumerate the existing checkpoints (and make sure we don't add the same checkpoint twice) // // Get the checkpoints count LONG lRegKeysCount = 0; CHECK_COM( pRegKeys->get_Count(&lRegKeysCount) ); // Iterate through all checkpoints on that resource for (INT nRegKeyIndex = 1; nRegKeyIndex <= lRegKeysCount; nRegKeyIndex++) { // Get the registry key CComVariant varRegKeyIndex = nRegKeyIndex; CVssComBSTR bstrRegKey; CHECK_COM( pRegKeys->get_Item(varRegKeyIndex, &bstrRegKey) ); // Check to see if this is our registry key if (!bstrRegKey || _wcsicmp(bstrRegKey, wszKeyPath)) continue; // We found an existing checkpoint with the same registry path return ft.Exit(false); } // Add the registry key CHECK_COM( pRegKeys->AddItem( bstrPath ) ); return ft.Exit(true); } // Removes the registry key from the cluster resource that corresponds to our disk. bool CVssClusterAPI::RemoveRegistryKey( IN LPCWSTR pwszVolumeName, IN LPCWSTR pwszPathFormat, IN ... ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::RemoveRegistryKey" ); ft.Trace( VSSDBG_GEN, L"Parameters: From = %s, Reg = %s", pwszVolumeName, pwszPathFormat); if ((pwszVolumeName == NULL) || (pwszPathFormat == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Build the path to the key WCHAR wszKeyPath[x_nVssMaxRegBuffer]; va_list marker; va_start( marker, pwszPathFormat ); ft.hr = StringCchVPrintfW( STRING_CCH_PARAM(wszKeyPath), pwszPathFormat, marker ); va_end( marker ); if (ft.HrFailed()) ft.TranslateGenericError(VSSDBG_GEN, ft.hr, L"StringCchVPrintfW()"); ft.Trace( VSSDBG_GEN, L"Formatted path: '%s'", wszKeyPath); // Get the corresponding resource CComPtr pResource = GetPhysicalDiskResourceForVolumeName(pwszVolumeName); if (pResource == NULL) { ft.Trace(VSSDBG_GEN, L"The volume is not shared. No registry key can be removed"); return ft.Exit(false); } // Get the Registry checkpoints collection CComPtr pRegKeys; CHECK_COM( pResource->get_RegistryKeys(&pRegKeys) ); // Get the checkpoints count LONG lRegKeysCount = 0; CHECK_COM( pRegKeys->get_Count(&lRegKeysCount) ); // Iterate through all checkpoints on that resource for (INT nRegKeyIndex = 1; nRegKeyIndex <= lRegKeysCount; nRegKeyIndex++) { // Get the registry key CComVariant varRegKeyIndex = nRegKeyIndex; CVssComBSTR bstrRegKey; CHECK_COM( pRegKeys->get_Item(varRegKeyIndex, &bstrRegKey) ); // Check to see if this is our registry key if (!bstrRegKey || _wcsicmp(bstrRegKey, wszKeyPath)) continue; // Remove the association CHECK_COM( pRegKeys->RemoveItem(varRegKeyIndex) ); return ft.Exit(true); } return ft.Exit(false); } // Create a task scheduler resource // Create the dependency from it to the Physical Disk Resource identified by the volume name // Then bring the resource online // - Returns TRUE if the TS resource was created // - Returns FALSE if no task needs to be created bool CVssClusterAPI::CreateTaskSchedulerResource( IN LPCWSTR pwszTaskSchedulerResourceName, // This will be the task name also IN LPCWSTR pwszApplicationName, IN LPCWSTR pwszApplicationParams, IN INT nTaskTriggersCount, IN PTASK_TRIGGER ptsTaskTriggersArray, IN LPCWSTR pwszMakeDependentOnVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::CreateTaskSchedulerResource" ); // Verify parameters ft.Trace( VSSDBG_GEN, L"Parameters: Task = %s, AppName = '%s', Params = '%s', Volume = '%s'", pwszTaskSchedulerResourceName, pwszApplicationName, pwszApplicationParams, pwszMakeDependentOnVolumeName); if ((pwszTaskSchedulerResourceName == NULL) || (pwszTaskSchedulerResourceName[0] == L'\0') || (pwszApplicationName == NULL) || (pwszApplicationName[0] == L'\0') || (pwszApplicationParams == NULL) || (pwszApplicationParams[0] == L'\0') || (nTaskTriggersCount <= 0) || (ptsTaskTriggersArray == NULL) || (pwszMakeDependentOnVolumeName == NULL) || (pwszMakeDependentOnVolumeName[0] == L'\0')) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the resource for the volume CComPtr pPhysicalDiskResource = GetPhysicalDiskResourceForVolumeName(pwszMakeDependentOnVolumeName); if (pPhysicalDiskResource == NULL) { ft.Trace(VSSDBG_GEN, L"The volume is not shared. No task scheduler can be added"); return ft.Exit(false); } // Get the resource group CComPtr pGroup; CHECK_COM( pPhysicalDiskResource->get_Group(&pGroup) ); // Get the resources list CComPtr pResources; CHECK_COM( pGroup->get_Resources(&pResources) ); // Build the resource name CVssComBSTR bstrResourceName = pwszTaskSchedulerResourceName; // Build the resource type CVssComBSTR bstrResourceType = x_ClusReg_TypeName_TaskScheduler; // Create our Task Scheduler resource CComPtr pResource; CHECK_COM( pResources->CreateItem( bstrResourceName, bstrResourceType, CLUSTER_RESOURCE_DEFAULT_MONITOR, &pResource) ); // Get the private properties collection CComPtr pProperties; CHECK_COM( pResource->get_PrivateProperties(&pProperties) ); // Add the Application Name private property CComPtr pProperty; CHECK_COM( pProperties->CreateItem( CVssComBSTR(x_ClusReg_Name_TaskScheduler_ApplicationName), CComVariant((BSTR)CVssComBSTR(pwszApplicationName)), &pProperty) ); // Add the Application Params private property pProperty = NULL; CHECK_COM( pProperties->CreateItem( CVssComBSTR(x_ClusReg_Name_TaskScheduler_ApplicationParams), CComVariant((BSTR)CVssComBSTR(pwszApplicationParams)), &pProperty) ); // Add the task trigger as a binary value CComVariant varTaskTrigger; CopyBinaryIntoVariant( (PBYTE)ptsTaskTriggersArray, sizeof(TASK_TRIGGER) * nTaskTriggersCount, varTaskTrigger); BS_ASSERT(varTaskTrigger.vt == (VT_ARRAY | VT_UI1)); BS_ASSERT(varTaskTrigger.parray->cDims == 1); BS_ASSERT(varTaskTrigger.parray->rgsabound[0].cElements == sizeof(TASK_TRIGGER) * nTaskTriggersCount); ft.Trace(VSSDBG_GEN, L"Writing a binary property with %d bytes", varTaskTrigger.parray->rgsabound[0].cElements); // Add the Trigger Array private property pProperty = NULL; CHECK_COM( pProperties->CreateItem( CVssComBSTR(x_ClusReg_Name_TaskScheduler_TriggerArray), varTaskTrigger, &pProperty) ); // Save changes CComVariant varStatusCode; CHECK_COM( pProperties->SaveChanges(&varStatusCode) ); BS_ASSERT(varStatusCode.vt == VT_ERROR); ft.hr = HRESULT_FROM_WIN32( varStatusCode.scode ); if (ft.HrFailed()) ft.TranslateComError( VSSDBG_GEN, L"pProperties->SaveChanges(&varStatusCode)"); // Add the dependency between this resource and the volume resource CComPtr pDependencies; CHECK_COM( pResource->get_Dependencies(&pDependencies) ); CHECK_COM( pDependencies->AddItem(pPhysicalDiskResource) ); // Bring the resource online // This will insert the task into the TS DB BringResourceOnline(pResource); return ft.Exit(true); } // Update task scheduler information bool CVssClusterAPI::UpdateTaskSchedulerResource( IN LPCWSTR pwszTaskSchedulerResourceName, IN INT nTaskTriggersCount, IN PTASK_TRIGGER ptsTaskTriggersArray ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::UpdateTaskSchedulerResource" ); // Verify parameters ft.Trace( VSSDBG_GEN, L"Parameters: Resource = %s, pTaskTrigger = %p", pwszTaskSchedulerResourceName, ptsTaskTriggersArray); if ((pwszTaskSchedulerResourceName == NULL) || (pwszTaskSchedulerResourceName[0] == L'\0') || (nTaskTriggersCount <= 0) || (ptsTaskTriggersArray == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the resources collection CComPtr pResources; BS_ASSERT(m_pCluster); CHECK_COM( m_pCluster->get_Resources(&pResources) ); // Get the resources count LONG lResourcesCount = 0; CHECK_COM( pResources->get_Count(&lResourcesCount) ); // Iterate through all resources bool bFound = false; CComPtr pResource; for (INT nResourceIndex = 1; nResourceIndex <= lResourcesCount; nResourceIndex++) { // Get the resource interface CComVariant varResourceIndex = nResourceIndex; pResource = NULL; CHECK_COM( pResources->get_Item(varResourceIndex, &pResource) ); // Ignore resources with type != "Task Scheduler" CVssComBSTR bstrTypeName; CHECK_COM( pResource->get_TypeName(&bstrTypeName) ); if (!bstrTypeName || wcscmp(x_ClusReg_TypeName_TaskScheduler, bstrTypeName)) { // ft.Trace(VSSDBG_GEN, L"Different type names: %s - %s", x_ClusReg_TypeName_TaskScheduler, bstrTypeName); continue; } // Ignore resources with a different name CVssComBSTR bstrResourceName; CHECK_COM( pResource->get_Name(&bstrResourceName) ); if (!bstrResourceName || wcscmp(pwszTaskSchedulerResourceName, bstrResourceName)) { // ft.Trace(VSSDBG_GEN, L"Different names: %s - %s", pwszTaskSchedulerResourceName, bstrResourceName); continue; } bFound = true; break; } if (!bFound) { ft.Trace(VSSDBG_GEN, L"Resource not found"); return ft.Exit(false); } // We found the resource (assuming the names uniquely identify a resource) // Get the private properties collection CComPtr pProperties; CHECK_COM( pResource->get_PrivateProperties(&pProperties) ); // Get the properties count LONG lPropertiesCount = 0; CHECK_COM( pProperties->get_Count(&lPropertiesCount) ); // Iterate through all properties bFound = false; CComPtr pProperty; for (INT nPropertyIndex = 1; nPropertyIndex <= lPropertiesCount; nPropertyIndex++) { // Get the resource type interface CComVariant varPropertyIndex = nPropertyIndex; pProperty = NULL; CHECK_COM( pProperties->get_Item(varPropertyIndex, &pProperty) ); // Get the property name CVssComBSTR bstrPropertyName; CHECK_COM( pProperty->get_Name(&bstrPropertyName) ); // Check to see if this is our "Trigger Array" property. // If not, continue if (!bstrPropertyName || wcscmp(bstrPropertyName, x_ClusReg_Name_TaskScheduler_TriggerArray)) continue; // We found the property bFound = true; break; } // If we didn't found that property, then return if (!bFound) { // The TriggerArray property is mandatory in our case BS_ASSERT(false); ft.LogGenericWarning(VSSDBG_COORD, L"pProperties->get_Item(varPropertyIndex, &pProperty) [%s, %s]", x_ClusReg_Name_TaskScheduler_TriggerArray, pwszTaskSchedulerResourceName); return ft.Exit(false); } // Add the task trigger as a binary value CComVariant varTaskTrigger; CopyBinaryIntoVariant( (PBYTE)ptsTaskTriggersArray, sizeof(TASK_TRIGGER) * nTaskTriggersCount, varTaskTrigger); // Update the Task Trigger private property CHECK_COM( pProperty->put_Value(varTaskTrigger) ); // Save changes CComVariant varStatusCode; CHECK_COM( pProperties->SaveChanges(&varStatusCode) ); // This error code means: The properties were stored but not all changes will // take effect until the next time the resource is brought online. BS_ASSERT(varStatusCode.vt == VT_ERROR); ft.hr = HRESULT_FROM_WIN32( varStatusCode.scode ); if (ft.hr != HRESULT_FROM_WIN32( ERROR_RESOURCE_PROPERTIES_STORED ) ) ft.TranslateComError( VSSDBG_GEN, L"pProperties->SaveChanges() => [0x%08lx]", ft.hr); // Take the resource offline // This will delete the task into the TS DB TakeResourceOffline(pResource); // Take the resource offline // This will insert the task into the TS DB BringResourceOnline(pResource); // Report success return ft.Exit(true); } // Delete a task scheduler resource. // Before that, take the resource offline and remove the dependency bool CVssClusterAPI::DeleteTaskSchedulerResource( IN LPCWSTR pwszTaskSchedulerResourceName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::DeleteTaskSchedulerResource" ); // Verify parameters ft.Trace( VSSDBG_GEN, L"Parameters: Resource = %s", pwszTaskSchedulerResourceName); if ((pwszTaskSchedulerResourceName == NULL) || (pwszTaskSchedulerResourceName[0] == L'\0')) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the resources collection CComPtr pResources; BS_ASSERT(m_pCluster); CHECK_COM( m_pCluster->get_Resources(&pResources) ); // Get the resources count LONG lResourcesCount = 0; CHECK_COM( pResources->get_Count(&lResourcesCount) ); // Iterate through all resources CComPtr pResource; for (INT nResourceIndex = 1; nResourceIndex <= lResourcesCount; nResourceIndex++) { // Get the resource interface CComVariant varResourceIndex = nResourceIndex; pResource = NULL; CHECK_COM( pResources->get_Item(varResourceIndex, &pResource) ); // Ignore resources with type != "Task Scheduler" CVssComBSTR bstrTypeName; CHECK_COM( pResource->get_TypeName(&bstrTypeName) ); if (!bstrTypeName || wcscmp(x_ClusReg_TypeName_TaskScheduler, bstrTypeName)) continue; // Ignore resources with a different name CVssComBSTR bstrResourceName; CHECK_COM( pResource->get_Name(&bstrResourceName) ); if (!bstrResourceName || wcscmp(pwszTaskSchedulerResourceName, bstrResourceName)) continue; // We found the resource (assuming the names uniquely identify a resource) // Take the resource offline // This will delete the task into the TS DB TakeResourceOffline(pResource); // Now delete the resource from the cluster (this will remove any dependencies) CHECK_COM( pResource->Delete() ); // Report success return ft.Exit(true); } return ft.Exit(false); } // Returns the Physical Disk resource that contains the given volume // If the volume doesn't belong to a physical disk resource, then return NULL // The returned volume must be freed with CoTaskMemFree ISClusResource* CVssClusterAPI::GetPhysicalDiskResourceForVolumeName( IN LPCWSTR pwszVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::GetPhysicalDiskResourceForVolumeName" ); // Verify parameters ft.Trace( VSSDBG_GEN, L"Parameters: Volume = %s", pwszVolumeName); if ((pwszVolumeName == NULL) || (pwszVolumeName[0] == L'\0')) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the resources collection CComPtr pResources; BS_ASSERT(m_pCluster); CHECK_COM( m_pCluster->get_Resources(&pResources) ); // Get the resources count LONG lResourcesCount = 0; CHECK_COM( pResources->get_Count(&lResourcesCount) ); // Iterate through all resources for (INT nResourceIndex = 1; nResourceIndex <= lResourcesCount; nResourceIndex++) { // Get the resource interface CComPtr pResource; CComVariant varResourceIndex = nResourceIndex; CHECK_COM( pResources->get_Item(varResourceIndex, &pResource) ); // Checks if relates with the partition if (IsResourceRefferingVolume(pResource, pwszVolumeName)) return ft.Exit(pResource.Detach()); } // No resource found return ft.Exit( (ISClusResource*)NULL ); } // Returns TRUE if a dependency can be established bool CVssClusterAPI::CanEstablishDependency( IN ISClusResource* pFromResource, // To be the dependent IN ISClusResource* pToResource // To be the dependency ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::CanEstablishDependency" ); // Verify parameters ft.Trace( VSSDBG_GEN, L"Parameters: pFromResource = %p, pToResource = %p", pFromResource, pToResource); if ((pFromResource == NULL) || (pToResource == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the name of the first resource CComVariant varBoolResult; CHECK_COM( pFromResource->CanResourceBeDependent(pToResource, &varBoolResult) ); BS_ASSERT( varBoolResult.vt == VT_BOOL ); bool bResult = (varBoolResult.boolVal == VARIANT_TRUE); return ft.Exit(bResult); } // Returns TRUE if a dependency is already established // - Returns TRUE if the dependency already exists // - Returns FALSE if the dependency could not be found // - Throws E_INVALIDARG on bad parameters bool CVssClusterAPI::IsDependencyAlreadyEstablished( IN ISClusResource* pFromResource, // Is dependent IN ISClusResource* pToResource // Is dependency ) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::IsDependencyAlreadyEstablished" ); // Verify parameters ft.Trace( VSSDBG_GEN, L"Parameters: pFromResource = %p, pToResource = %p", pFromResource, pToResource); if ((pFromResource == NULL) || (pToResource == NULL)) { BS_ASSERT(false); ft.Throw(VSSDBG_GEN, E_INVALIDARG, L"NULL parameters"); } // Get the dependents collection for the "FROM" resource CComPtr pDependencies; CHECK_COM( pFromResource->get_Dependencies(&pDependencies) ); // Get the dependencies count LONG lDependenciesCount = 0; CHECK_COM( pDependencies->get_Count(&lDependenciesCount) ); // Iterate through all dependencies for (INT nDependencyIndex = 1; nDependencyIndex <= lDependenciesCount; nDependencyIndex++) { // Get the dependency with that index CComPtr pDependencyResource; CComVariant varDependencyIndex = nDependencyIndex; CHECK_COM( pDependencies->get_Item(varDependencyIndex, &pDependencyResource) ); // Check to see if this is our resource. If not, continue if (!AreResourcesEqual(pToResource, pDependencyResource)) continue; // We find the dependency return ft.Exit(true); } return ft.Exit(false); } // Returns true if hte volume belongs to a Physical Disk resource bool CVssClusterAPI::IsVolumeBelongingToPhysicalDiskResource( IN LPCWSTR pwszVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::IsVolumeBelongingToPhysicalDiskResource" ); if (!m_pCluster) return ft.Exit(false); CComPtr ptrResource = GetPhysicalDiskResourceForVolumeName(pwszVolumeName); return ft.Exit(ptrResource != NULL); } // Returns TRUE if the resource is a Physical Disk resource that contains the given volume bool CVssClusterAPI::IsResourceRefferingVolume( IN ISClusResource* pResource, IN LPCWSTR pwszVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::IsResourceRefferingVolume" ); BS_ASSERT(pResource); BS_ASSERT(pwszVolumeName); CVssComBSTR bstrName; CHECK_COM( pResource->get_Name(&bstrName) ); ft.Trace( VSSDBG_GEN, L"Parameters: resource name = %s, pwszVolumeName = %s", (LPWSTR) bstrName, pwszVolumeName); // Ignore resources that are not in the storage class CLUSTER_RESOURCE_CLASS rcClassInfo; CHECK_COM( pResource->get_ClassInfo(&rcClassInfo) ); if (rcClassInfo != CLUS_RESCLASS_STORAGE) return ft.Exit(false); // Ignore resources with type != "Physical Disk" CVssComBSTR bstrTypeName; CHECK_COM( pResource->get_TypeName(&bstrTypeName) ); if (!bstrTypeName || wcscmp(x_ClusReg_TypeName_PhysicalDisk, bstrTypeName)) return ft.Exit(false); // // Get the list of volume MpM guids // // Look into the MULTI_SZ string for our volume // We assume the format of one string is // "[byte offset] [volume in kernel-mode format]" // // This private property is described in the following documentation: // \\index1\sdnt\commontest\wtt\wttwebddb\webdocs\management\mount_point_support.doc // // Get the private properties collection CComPtr pProperties; CHECK_COM( pResource->get_PrivateProperties(&pProperties) ); // Get the properties count LONG lPropertiesCount = 0; CHECK_COM( pProperties->get_Count(&lPropertiesCount) ); // Iterate through all properties bool bFound = false; CComPtr pProperty; for (INT nPropertyIndex = 1; nPropertyIndex <= lPropertiesCount; nPropertyIndex++) { // Get the resource type interface CComVariant varPropertyIndex = nPropertyIndex; pProperty = NULL; CHECK_COM( pProperties->get_Item(varPropertyIndex, &pProperty) ); // Get the property name CVssComBSTR bstrPropertyName; CHECK_COM( pProperty->get_Name(&bstrPropertyName) ); // Check to see if this is our "Mount Volume Name" property. // If not, continue if (!bstrPropertyName || wcscmp(bstrPropertyName, x_ClusReg_Name_PhysicalDisk_MPVolGuids)) continue; // We found the property bFound = true; break; } // If we didn't found that (optional) property, then return if (!bFound) return ft.Exit(false); // Copy the volume locally in order to convert it CVssAutoLocalString strVolumeName; strVolumeName.CopyFrom(pwszVolumeName); // Convert the volume name into the Kernel-mode representation if (!ConvertVolMgmtVolumeNameIntoKernelObject( strVolumeName )) { // Programming error - the original volume is not in the \\?\Volume{GUID}\ format BS_ASSERT(false); ft.TranslateGenericError(VSSDBG_GEN, E_UNEXPECTED, L"ConvertVolMgmtVolumeNameIntoKernelObject(%s)", strVolumeName); } // Get the value list CComPtr pValues; CHECK_COM( pProperty->get_Values(&pValues) ); // Get the values count LONG lValueCount = 0; CHECK_COM( pValues->get_Count(&lValueCount) ); ft.Trace(VSSDBG_GEN, L"Number of volumes: %ld", lValueCount); // Iterate through all the values CComPtr pValue; for (INT nValueIndex = 1; nValueIndex <= lValueCount; nValueIndex++) { // Get the resource type interface CComVariant varValueIndex = nValueIndex; pValue = NULL; CHECK_COM( pValues->get_Item(varValueIndex, &pValue) ); // Get the count of Data values. It must be a positive number LONG lDataCount; CHECK_COM( pValue->get_DataCount(&lDataCount) ); // Ignore properties with no data if (lDataCount <=0) { BS_ASSERT(false); continue; } // Get the Data collection CComPtr pData; CHECK_COM( pValue->get_Data(&pData) ); // Get the count of Data values. // Just verify that is the same number LONG lInternalDataCount; CHECK_COM( pData->get_Count(&lInternalDataCount) ); BS_ASSERT(lInternalDataCount == lDataCount); // Get the property value. CComVariant varVolumeMountPoint; for (INT nDataIndex = 1; nDataIndex <= lDataCount; nDataIndex++) { CComVariant varDataIndex = nDataIndex; varVolumeMountPoint.Clear(); CHECK_COM( pData->get_Item(varDataIndex, &varVolumeMountPoint) ); // This has to be a BSTR if (varVolumeMountPoint.vt != VT_BSTR) { BS_ASSERT(false); ft.TranslateGenericError(VSSDBG_GEN, E_UNEXPECTED, L"pProperty->get_Value() != VT_BSTR"); } LPWSTR pwszMountPoint = varVolumeMountPoint.bstrVal; ft.Trace(VSSDBG_GEN, L"Processing volume: %s", pwszMountPoint); // Skip the hex number composing the internal offset for(;isalnum(pwszMountPoint[0]);pwszMountPoint++); // Skip the spaces after the number for(;iswspace(pwszMountPoint[0]);pwszMountPoint++); // Now we get to a volume name (according to the current doc). Compare it to the original volume. if (_wcsicmp(pwszMountPoint, strVolumeName) == 0) return ft.Exit(true); } } return ft.Exit(false); } // returns TRUE if the two COM objects are identifying the same resource bool CVssClusterAPI::AreResourcesEqual( IN ISClusResource* pResource1, IN ISClusResource* pResource2 ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::AreResourcesEqual" ); BS_ASSERT(pResource1); BS_ASSERT(pResource2); // Get the name of the first resource CVssComBSTR bstrName1; CHECK_COM( pResource1->get_Name(&bstrName1) ); // Get the name of the second resource CVssComBSTR bstrName2; CHECK_COM( pResource2->get_Name(&bstrName2) ); ft.Trace( VSSDBG_GEN, L"Comparing: resource = %s, resource = %s", (LPWSTR) bstrName1, (LPWSTR) bstrName2); if (bstrName1 != bstrName2) return ft.Exit(false); return ft.Exit(true); } // Copy the given binary data into the variant void CVssClusterAPI::CopyBinaryIntoVariant( IN PBYTE pbData, IN DWORD cbSize, IN OUT CComVariant & variant ) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::CopyBinaryIntoVariant" ); // Describe the new array SAFEARRAYBOUND sab[ 1 ]; sab[ 0 ].lLbound = 0; sab[ 0 ].cElements = cbSize; // allocate a one dimensional SafeArray of BYTES CVssAutoSafearrayPtr pSafeArray = ::SafeArrayCreate( VT_UI1, 1, sab ); if (!pSafeArray.IsValid()) ft.ThrowOutOfMemory(VSSDBG_GEN); // get a pointer to the SafeArray PBYTE pbArrayData = NULL; ft.hr = ::SafeArrayAccessData( pSafeArray, (PVOID *) &pbArrayData ); if ( ft.HrFailed() ) ft.TranslateGenericError(VSSDBG_GEN, ft.hr, L"::SafeArrayAccessData( pSafeArray, (PVOID *) &pb )"); // transfer data into the array ::CopyMemory( pbArrayData, pbData, cbSize ); // release the pointer into the SafeArray ft.hr = ::SafeArrayUnaccessData( pSafeArray ); if ( ft.HrFailed() ) ft.TranslateGenericError(VSSDBG_GEN, ft.hr, L"::SafeArrayUnaccessData( pSafeArray )"); // Clear the variant variant.Clear(); // tell the variant what it is holding onto variant.parray = pSafeArray.Detach(); variant.vt = VT_ARRAY | VT_UI1; } // Take the resource offline void CVssClusterAPI::TakeResourceOffline( IN ISClusResource* pResource ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::TakeResourceOffline" ); BS_ASSERT(pResource); // Get resource name CVssComBSTR bstrName; CHECK_COM( pResource->get_Name(&bstrName) ); // Take the resource offline CComVariant varStillPending; CHECK_COM( pResource->Offline(m_dwOfflineTimeout, &varStillPending) ); BS_ASSERT( varStillPending.vt == VT_BOOL ); if (varStillPending.boolVal == VARIANT_TRUE) { ft.LogGenericWarning(VSSDBG_GEN, L"pResource->Offline(%ld, [VT_TRUE]) [%s]", (LONG)m_dwOfflineTimeout, (LPWSTR)bstrName); ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"The resource [%s] couldn't be brought offline in the required timeframe.", (LPWSTR)bstrName); } } // Bring the resource online void CVssClusterAPI::BringResourceOnline( IN ISClusResource* pResource ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::BringResourceOnline" ); BS_ASSERT(pResource); // Get resource name CVssComBSTR bstrName; CHECK_COM( pResource->get_Name(&bstrName) ); // Bring the resource online CComVariant varStillPending; CHECK_COM( pResource->Online(m_dwOnlineTimeout, &varStillPending) ); BS_ASSERT( varStillPending.vt == VT_BOOL ); if (varStillPending.boolVal == VARIANT_TRUE) { ft.LogGenericWarning(VSSDBG_GEN, L"pResource->Online(%ld, [VT_TRUE]) [%s]", (LONG)m_dwOnlineTimeout, (LPWSTR)bstrName); ft.Throw( VSSDBG_GEN, E_UNEXPECTED, L"The resource [%s] couldn't be brought online in the required timeframe.", (LPWSTR)bstrName); } } // Calculate the list of dependent resources that needs to be brought online after adding a // dependency fron this resource void CVssClusterAPI::GetFinalOnlineResourceList( IN ISClusResource* pResource, IN OUT CVssClusterResourceList & list ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::GetFinalOnlineResourceList" ); // Determine if the resource is online. // If not, we stop here. CLUSTER_RESOURCE_STATE csState; CHECK_COM( pResource->get_State(&csState) ); if (csState != ClusterResourceOnline) return; // Start with one resource only CVssClusterResourceList frontier; frontier.Push(pResource); while (true) { CComPtr pCurrentResource; frontier.Pop(pCurrentResource); // If the frontier is empty, the we are finished (the dependency graph cannot contain cycles) if (pCurrentResource == NULL) break; // Get the dependents collection for the current resource CComPtr pDependents; CHECK_COM( pCurrentResource->get_Dependents(&pDependents) ); // Get the dependencies count LONG lDependentsCount = 0; CHECK_COM( pDependents->get_Count(&lDependentsCount) ); // Iterate through all dependencies DWORD dwOnlineDependentResources = 0; for (INT nDependentIndex = 1; nDependentIndex <= lDependentsCount; nDependentIndex++) { // Get the dependency with that index CComPtr pDependentResource; CComVariant varDependentIndex = nDependentIndex; CHECK_COM( pDependents->get_Item(varDependentIndex, &pDependentResource) ); // Determine if the resource is online. // If not, we ignore it. CHECK_COM( pDependentResource->get_State(&csState) ); if (csState != ClusterResourceOnline) continue; // We add this resource to the frontier frontier.Push(pDependentResource); dwOnlineDependentResources++; } // If this was a final resource (no dependents added to the list) then add it to the final list if (dwOnlineDependentResources == 0) list.Push(pCurrentResource); } } // Bring the resources in the list online void CVssClusterAPI::BringResourceListOnline( IN CVssClusterResourceList & list ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::BringResourceListOnline" ); while (true) { CComPtr pCurrentResource; list.Pop(pCurrentResource); // If the frontier is empty, then we are finished (the dependency graph cannot contain cycles) if (pCurrentResource == NULL) break; // Determine if the resource is online. // If not, we ignore it. CLUSTER_RESOURCE_STATE csState; CHECK_COM( pCurrentResource->get_State(&csState) ); if (csState != ClusterResourceOffline) continue; // Bring this resource online BringResourceOnline(pCurrentResource); } } // Bring the resources in the list online void CVssClusterAPI::GetQuorumPath( CComBSTR & bstrQuorumPath ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterAPI::GetQuorumPath" ); bstrQuorumPath.Empty(); // Get the path to the Quorum log file BS_ASSERT(m_pCluster); CHECK_COM( m_pCluster->get_QuorumPath(&bstrQuorumPath) ); ft.Trace( VSSDBG_GEN, L"Quorum file path: %s", bstrQuorumPath); if ((NULL == (LPWSTR)bstrQuorumPath) || (L'\0' == bstrQuorumPath[0])) { BS_ASSERT(false); ft.TranslateGenericError(VSSDBG_GEN, E_UNEXPECTED, L"get_QuorumPath([NULL])"); } } ///////////////////////////////////////////////////////////////////////////// // CVssClusterQuorumVolume definition and implementation // // Note: this class is separate from CVssClusterAPI so its instances can be static. // Checks if the given volume is the quorum // This will return FALSE in non-cluster case. bool CVssClusterQuorumVolume::IsQuorumVolume( IN LPCWSTR pwszVolumeName ) throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterQuorumVolume::IsQuorumVolume" ); // If the volume is not initialized, then try to initialize it if (!m_bQuorumVolumeInitialized) InitializeQuorumVolume(); // Return TRUE only if this is the quorum volume return ft.Exit(m_awszQuorumVolumeName.GetRef() && (wcscmp(m_awszQuorumVolumeName.GetRef(), pwszVolumeName) == 0)); } // Checks if the given volume is the quorum // This will work in non-cluster case also. void CVssClusterQuorumVolume::InitializeQuorumVolume() throw(HRESULT) { CVssFunctionTracer ft( VSSDBG_GEN, L"CVssClusterQuorumVolume::InitializeQuorumVolume" ); BS_ASSERT(!m_bQuorumVolumeInitialized) // Mark this as being already initialized // (to not recompute the quorum each time we call IsQuorumVolume) m_bQuorumVolumeInitialized = true; // Try to initialize the cluster, if possibles CVssClusterAPI clus; bool bClusterInitialized = clus.Initialize(L""); // If we don't have a cluster, then we don't have quorum volume. if (!bClusterInitialized) return; // Get the quorum path CComBSTR bstrQuorumPath; clus.GetQuorumPath(bstrQuorumPath); // // Test if the path starts with a drive letter // NOTE: We assume that the path is a drive letter (we ignore shares) // // 1) We should have room at least for ":\" if (wcslen(bstrQuorumPath) < 3) return; // 2) This is not at share if (bstrQuorumPath[0] == L'\\') return; // 3) Test if the rest of the path matches ":\" if (bstrQuorumPath[1] != L':') return; if (bstrQuorumPath[2] != L'\\') return; // Get the volume path WCHAR wszQuorumVolume[MAX_PATH]; if (!GetVolumePathNameW(bstrQuorumPath, STRING_CCH_PARAM(wszQuorumVolume))) { switch(GetLastError()) { // The volume might be offline case ERROR_FILE_NOT_FOUND: case ERROR_DEVICE_NOT_CONNECTED: case ERROR_NOT_READY: return; // Otherwise default: ft.TranslateWin32Error(VSSDBG_GEN, L"GetVolumePathNameW('%s', STRING_CCH_PARAM(wszQuorumVolumePath))", bstrQuorumPath); } } // Get the volume name WCHAR wszVolumeName[x_nLengthOfVolMgmtVolumeName + 1]; if (!GetVolumeNameForVolumeMountPointW( wszQuorumVolume, STRING_CCH_PARAM(wszVolumeName) )) { switch(GetLastError()) { // The volume might be offline case ERROR_FILE_NOT_FOUND: case ERROR_DEVICE_NOT_CONNECTED: case ERROR_NOT_READY: return; // Otherwise default: ft.TranslateWin32Error(VSSDBG_GEN, L"GetVolumeNameForVolumeMountPointW( '%s', STRING_CCH_PARAM(wszVolumeName))", wszQuorumVolume); } } ft.Trace( VSSDBG_GEN, L"Quorum volume name: %s", wszVolumeName); // Cache the quorum volume m_awszQuorumVolumeName.CopyFrom(wszVolumeName); }