|
|
/*
**++ ** ** Copyright (c) 2002 Microsoft Corporation ** ** ** Module Name: ** ** swriter.cpp ** ** ** Abstract: ** ** Test program to to register a Writer with various properties ** ** Author: ** ** Reuven Lax [reuvenl] 04-June-2002 ** ** ** Revision History: ** **-- */
///////////////////////////////////////////////////////////////////////////////
// Includes
#include "stdafx.h"
#include "swriter.h"
#include "utility.h"
#include "writerconfig.h"
#include <string>
#include <sstream>
#include <functional>
#include <algorithm>
#include <queue>
///////////////////////////////////////////////////////////////////////////////
// Declarations and Definitions
using std::wstring; using std::string; using std::wstringstream; using std::exception; using std::vector;
using Utility::checkReturn; using Utility::warnReturn; using Utility::printStatus;
static const wchar_t* const BackupString = L"BACKUP"; static const wchar_t* const RestoreString = L"RESTORE";
///////////////////////////////////////////////////////////////////////////////
// Initialize the test writer
HRESULT STDMETHODCALLTYPE TestWriter::Initialize() { WriterConfiguration* config = WriterConfiguration::instance();
printStatus(L"Initializing Writer", Utility::high); HRESULT hr = CVssWriter::Initialize(TestWriterId, // WriterID
TestWriterName, // wszWriterName
config->usage(), // ut
VSS_ST_OTHER); // st
checkReturn(hr, L"CVssWriter::Initialize"); hr = Subscribe(); checkReturn(hr, L"CVssWriter::Subscribe");
return S_OK; }
// OnIdentify is called as a result of the requestor calling GatherWriterMetadata
// Here we report the writer metadata using the passed-in interface
bool STDMETHODCALLTYPE TestWriter::OnIdentify(IN IVssCreateWriterMetadata *pMetadata) try { enterEvent(Utility::Identify); WriterConfiguration* config = WriterConfiguration::instance();
// set the restore method properly
RestoreMethod method = config->restoreMethod(); HRESULT hr = pMetadata->SetRestoreMethod(method.m_method, method.m_service.c_str(), NULL, method.m_writerRestore, method.m_rebootRequired); checkReturn(hr, L"IVssCreateWriterMetadata::SetRestoreMethod"); printStatus(L"\nSet restore method: ", Utility::high); printStatus(method.toString(), Utility::high); // set the alternate-location list
RestoreMethod::AlternateList::iterator currentAlt = method.m_alternateLocations.begin(); while (currentAlt != method.m_alternateLocations.end()) { hr = pMetadata->AddAlternateLocationMapping(currentAlt->m_path. c_str(), currentAlt->m_filespec.c_str(), currentAlt->m_recursive, currentAlt->m_alternatePath.substr(0, currentAlt->m_alternatePath.size()-1).c_str()); checkReturn(hr, L"IVssCreateWriterMetadata::AddAlternateLocationMapping");
printStatus(L"\nAdded Alternate Location Mapping"); printStatus(currentAlt->toString()); ++currentAlt; }
// set the exclude-file list
WriterConfiguration::ExcludeFileList::iterator currentExclude = config->excludeFiles().begin(); while (currentExclude != config->excludeFiles().end()) { hr = pMetadata->AddExcludeFiles(currentExclude->m_path.c_str(), currentExclude->m_filespec.c_str(), currentExclude->m_recursive); checkReturn(hr, L"IVssCreateWriterMetadata::AddExcludeFiles");
printStatus(L"\nAdded exclude filespec"); printStatus(currentExclude->toString()); ++currentExclude; }
// add all necessary components
WriterConfiguration::ComponentList::iterator currentComponent = config->components().begin(); while (currentComponent != config->components().end()) { addComponent(*currentComponent, pMetadata); ++currentComponent; }
return true; } catch(const exception& thrown) { printStatus(string("Failure in Identify event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; };
// This function is called as a result of the requestor calling PrepareForBackup
// Here we do some checking to ensure that the requestor selected components properly
bool STDMETHODCALLTYPE TestWriter::OnPrepareBackup(IN IVssWriterComponents *pComponents) try { enterEvent(Utility::PrepareForBackup); WriterConfiguration* config = WriterConfiguration::instance();
// get the number of components
UINT numComponents = 0; HRESULT hr = pComponents->GetComponentCount(&numComponents); checkReturn(hr, L"IVssWriterComponents::GetComponentCount");
// we haven't defined CUSTOM restore method for this writer. consequently, backup apps should
// ignore it.
if ((config->restoreMethod().m_method == VSS_RME_CUSTOM) && numComponents > 0) { throw Utility::TestWriterException(L"Components were selected for backup when CUSTOM restore" L" method was used. This is incorrect"); }
m_selectedComponents.clear();
// for each component that was added
for (unsigned int x = 0; x < numComponents; x++) { // --- get the relevant information
CComPtr<IVssComponent> pComponent; hr = pComponents->GetComponent(x, &pComponent); checkReturn(hr, L"IVssWriterComponents::GetComponent");
writeBackupMetadata(pComponent); // --- write private metadata
// --- find the component in the metadata document.
// --- this component may actually be a supercomponent of something
// --- listed in the metadata document, so we must handle that case.
// --- this is no longer true with the new interface changes... now, only components
// --- in the metadata doc can be added
ComponentBase identity(getPath(pComponent), getName(pComponent)); WriterConfiguration::ComponentList::iterator found = std::find(config->components().begin(), config->components().end(), identity); if (found == config->components().end()) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L" and name: " << identity.m_name << L" was added to the document" << std::endl << L", but does not appear in the writer metadata"; printStatus(msg.str()); } else if (!addableComponent(*found)) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L" and name: " << identity.m_name << L" was added to the document" << std::endl << L", but is not a selectable component"; printStatus(msg.str()); } else { m_selectedComponents.push_back(*found); } }
// any non-selectable component with no selectable ancestor must be added. Check this.
vector<Component> mustAddComponents; buildContainer_if(config->components().begin(), config->components().end(), std::back_inserter(mustAddComponents), Utility::and1(std::not1(std::ptr_fun(isComponentSelectable)), std::ptr_fun(addableComponent)));
vector<Component>::iterator currentMust = mustAddComponents.begin(); while (currentMust != mustAddComponents.end()) { if (std::find(m_selectedComponents.begin(), m_selectedComponents.end(), *currentMust) == m_selectedComponents.end()) { wstringstream msg; msg << L"\nComponent with logical path: " << currentMust->m_logicalPath << L" and name: " << currentMust->m_name << L" is a non-selectable component with no selectable ancestor, and therefore " << L" must be added to the document. However, it was not added"; printStatus(msg.str()); } ++currentMust; } return true; } catch(const exception& thrown) { printStatus(string("Failure in PrepareForBackup event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; };
// This function is called after a requestor calls DoSnapshotSet
// Here we ensure that the requestor has added the appropriate volumes to the
// snapshot set. If a spit directory is specified, the spit is done here as well.
bool STDMETHODCALLTYPE TestWriter::OnPrepareSnapshot() try { enterEvent(Utility::PrepareForSnapshot);
// build the list of all files being backed up
vector<TargetedFile> componentFiles; std::pointer_to_binary_function<Component, std::back_insert_iterator<vector<TargetedFile> >, void> ptrFun(buildComponentFiles); std::for_each(m_selectedComponents.begin(), m_selectedComponents.end(), std::bind2nd(ptrFun, std::back_inserter(componentFiles))); // for every file being backed up
vector<TargetedFile>::iterator currentFile = componentFiles.begin(); while (currentFile != componentFiles.end()) { // --- ensure the filespec has been snapshot, taking care of mount points
if (!checkPathAffected(*currentFile)) { wstringstream msg; msg << L"Filespec " << currentFile->m_path << currentFile->m_filespec << L"is selected for backup but contains files that have not been snapshot" << std::endl; printStatus(msg.str()); }
// --- if a spit is needed, spit the file to the proper directory
if (!currentFile->m_alternatePath.empty()) spitFiles(*currentFile); ++currentFile; } return true; } catch(const exception& thrown) { printStatus(string("Failure in PrepareForSnapshot event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called after a requestor calls DoSnapshotSet
// Currently, we don't do much here that is interesting.
bool STDMETHODCALLTYPE TestWriter::OnFreeze() try { enterEvent(Utility::Freeze);
return true; } catch(const exception& thrown) { printStatus(string("Failure in Freeze event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called after a requestor calls DoSnapshotSet
// Currently, we don't do much here that is interesting.
bool STDMETHODCALLTYPE TestWriter::OnThaw() try { enterEvent(Utility::Thaw);
return true; } catch(const exception& thrown) { printStatus(string("Failure in Thaw event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called after a requestor calls DoSnapshotSet
// Here we cleanup the files that were spit in OnPrepareSnapshot
// and do some basic sanity checking
bool STDMETHODCALLTYPE TestWriter::OnPostSnapshot(IN IVssWriterComponents *pComponents) try { enterEvent(Utility::PostSnapshot);
cleanupFiles(); // get the number of components
UINT numComponents = 0; HRESULT hr = pComponents->GetComponentCount(&numComponents); checkReturn(hr, L"IVssWriterComponents::GetComponentCount");
// for each component that was added
for (unsigned int x = 0; x < numComponents; x++) { // --- get the relevant information
CComPtr<IVssComponent> pComponent; hr = pComponents->GetComponent(x, &pComponent); checkReturn(hr, L"IVssWriterComponents::GetComponent"); // --- ensure that the component was backed up
ComponentBase identity(getPath(pComponent), getName(pComponent)); vector<Component>::iterator found = std::find(m_selectedComponents.begin(), m_selectedComponents.end(), identity); if (found == m_selectedComponents.end()) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L"was selected in PostSnapshot, but was not selected in PrepareForSnapshot"; printStatus(msg.str(), Utility::low); continue; }
if (!verifyBackupMetadata(pComponent)) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" has been corrupted in PostSnapshot"; printStatus(msg.str(), Utility::low); } }
m_selectedComponents.clear();
return true; } catch(const exception& thrown) { printStatus(string("Failure in PostSnapshot event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called to abort the writer's backup sequence.
// If the writer has a spit component, spit files are cleaned up here.
bool STDMETHODCALLTYPE TestWriter::OnAbort() try { enterEvent(Utility::Abort);
m_selectedComponents.clear(); cleanupFiles();
return true; } catch(const exception& thrown) { printStatus(string("Failure in Abort event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called as a result of the requestor calling BackupComplete
// Once again we do sanity checking, and we also verify that the metadata we
// wrote in PrepareForBackup has remained the same
bool STDMETHODCALLTYPE TestWriter::OnBackupComplete(IN IVssWriterComponents *pComponents) try { enterEvent(Utility::BackupComplete); WriterConfiguration* config = WriterConfiguration::instance();
// get the number of components
UINT numComponents = 0; HRESULT hr = pComponents->GetComponentCount(&numComponents); checkReturn(hr, L"IVssWriterComponents::GetComponentCount");
// for each component that was added
for (unsigned int x = 0; x < numComponents; x++) { // --- get the relevant information
CComPtr<IVssComponent> pComponent; hr = pComponents->GetComponent(x, &pComponent); checkReturn(hr, L"IVssWriterComponents::GetComponent"); // --- ensure that the component is valid
ComponentBase identity(getPath(pComponent), getName(pComponent)); WriterConfiguration::ComponentList::iterator found = std::find(config->components().begin(), config->components().end(), identity); if (found == config->components().end()) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" is selected in BackupComplete, but does not appear in the writer metadata"; printStatus(msg.str(), Utility::low); continue; }
if (!verifyBackupMetadata(pComponent)) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" has been corrupted in BackupComplete"; printStatus(msg.str(), Utility::low); }
// check that the backup succeeded
bool backupSucceeded = false; hr = pComponent->GetBackupSucceeded(&backupSucceeded); if (!backupSucceeded) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" was not marked as successfully backed up."; } }
return true; } catch(const exception& thrown) { printStatus(string("Failure in BackupComplete event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called at the end of the backup process. This may happen as a result
// of the requestor shutting down, or it may happen as a result of abnormal termination
// of the requestor.
bool STDMETHODCALLTYPE TestWriter::OnBackupShutdown(IN VSS_ID SnapshotSetId) try { UNREFERENCED_PARAMETER(SnapshotSetId); enterEvent(Utility::BackupShutdown); return true; } catch(const exception& thrown) { printStatus(string("Failure in BackupShutdown event: ") + thrown.what(), Utility::low); return false; }
// This function is called as a result of the requestor calling PreRestore
// We check that component selection has been done properly, verify the
// backup metadata, and set targets appropriately.
bool STDMETHODCALLTYPE TestWriter::OnPreRestore(IN IVssWriterComponents *pComponents) try { enterEvent(Utility::PreRestore);
WriterConfiguration* config = WriterConfiguration::instance();
// get the number of components
UINT numComponents = 0; HRESULT hr = pComponents->GetComponentCount(&numComponents); checkReturn(hr, L"IVssWriterComponents::GetComponentCount");
m_selectedRestoreComponents.clear(); // for each component that was added
for (unsigned int x = 0; x < numComponents; x++) { // --- get the relevant information
CComPtr<IVssComponent> pComponent; hr = pComponents->GetComponent(x, &pComponent); checkReturn(hr, L"IVssWriterComponents::GetComponent"); // --- ensure that the component is valid
ComponentBase identity(getPath(pComponent), getName(pComponent)); WriterConfiguration::ComponentList::iterator found = std::find(config->components().begin(), config->components().end(), identity); if (found == config->components().end()) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" is selected in PreRestore, but does not appear in the writer metadata";
pComponent->SetPreRestoreFailureMsg(msg.str().c_str()); printStatus(msg.str(), Utility::low); continue; }
// only process those component that are selected for restore
bool selectedForRestore = false; hr = pComponent->IsSelectedForRestore(&selectedForRestore); checkReturn(hr, L"IVssComponent::IsSelectedForRestore"); if (!selectedForRestore) continue; m_selectedRestoreComponents.push_back(*found);
if (!verifyBackupMetadata(pComponent)) { // --- verify the backup metadata
wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" has been corrupted in PreRestore"; pComponent->SetPreRestoreFailureMsg(msg.str().c_str()); printStatus(msg.str(), Utility::low); } writeRestoreMetadata(pComponent); // --- write restore metadata
// --- set the target appropriately
if (found->m_restoreTarget != VSS_RT_UNDEFINED) { HRESULT hr =pComponent->SetRestoreTarget(found->m_restoreTarget); checkReturn(hr, L"IVssComponent::SetRestoreTarget");
printStatus(wstring(L"Set Restore Target: ") + Utility::toString(found->m_restoreTarget), Utility::high); } }
return true; } catch(const exception& thrown) { printStatus(string("Failure in PreRestore event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called as a result of the requestor calling PreRestore
// We do some sanity checking, and then check to see if files have indeed
// been restored
bool STDMETHODCALLTYPE TestWriter::OnPostRestore(IN IVssWriterComponents *pComponents) try { enterEvent(Utility::PostRestore);
// get the number of components
UINT numComponents = 0; HRESULT hr = pComponents->GetComponentCount(&numComponents); checkReturn(hr, L"IVssWriterComponents::GetComponentCount");
// for each component
for (unsigned int x = 0; x < numComponents; x++) { // --- get the relevant information
CComPtr<IVssComponent> pComponent; hr = pComponents->GetComponent(x, &pComponent); checkReturn(hr, L"I VssWriterComponents::GetComponent");
// --- ensure that the component is valid
ComponentBase identity(getPath(pComponent), getName(pComponent)); vector<Component>::iterator found = std::find(m_selectedRestoreComponents.begin(), m_selectedRestoreComponents.end(), identity); if (found == m_selectedRestoreComponents.end()) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" is selected in PostRestore, but was not selected in PreRestore"; pComponent->SetPostRestoreFailureMsg(msg.str().c_str()); printStatus(msg.str(), Utility::low); continue; }
// only process those component that are selected for restore
bool selectedForRestore = false; hr = pComponent->IsSelectedForRestore(&selectedForRestore); checkReturn(hr, L"IVssComponent::IsSelectedForRestore"); if (!selectedForRestore) continue;
if (!verifyRestoreMetadata(pComponent)) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" has been corrupted in PostRestore"; pComponent->SetPostRestoreFailureMsg(msg.str().c_str()); printStatus(msg.str(), Utility::low); continue; }
VSS_FILE_RESTORE_STATUS rStatus; hr = pComponent->GetFileRestoreStatus(&rStatus); checkReturn(hr, L"IVssComponent::GetFileRestoreStatus");
if (rStatus != VSS_RS_ALL) { wstringstream msg; msg << L"Component with logical path: " << identity.m_logicalPath << L"and name: " << identity.m_name << L" was not marked as having been successfully restored"; printStatus(msg.str(), Utility::low); continue; }
updateNewTargets(pComponent, *found); verifyFilesRestored(pComponent, *found); }
return true; } catch(const exception& thrown) { printStatus(string("Failure in PostRestore event: ") + thrown.what(), Utility::low); return false; } catch(HRESULT error) { Utility::TestWriterException e(error); printStatus(e.what(), Utility::low); return false; }
// This function is called at the entry to all writer events.
// A status message is printed to the console, and the event is failed if necessary.
void TestWriter::enterEvent(Utility::Events event) { static HRESULT errors[] = { VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT, VSS_E_WRITERERROR_OUTOFRESOURCES, VSS_E_WRITERERROR_TIMEOUT, VSS_E_WRITERERROR_RETRYABLE }; printStatus(wstring(L"\nReceived event: ") + Utility::toString(event));
WriterConfiguration* config = WriterConfiguration::instance();
// figure out whether we should fail this event
WriterEvent writerEvent(event); WriterConfiguration::FailEventList::iterator found = std::find(config->failEvents().begin(), config->failEvents().end(), writerEvent);
// if so, then fail it unless failures have run out
if (found != config->failEvents().end()) { bool failEvent = !found->m_retryable || (m_failures[event] < found->m_numFailures); bool setFailure = inSequence(event); if (!found->m_retryable && setFailure) SetWriterFailure(VSS_E_WRITERERROR_NONRETRYABLE); else if (failEvent && setFailure) SetWriterFailure(errors[rand() % (sizeof(errors) / sizeof(errors[0]))]);
if (failEvent) { ++m_failures[event]; wstringstream msg; msg << L"Failure Requested in Event: " << Utility::toString(event) << L" Failing for the " << m_failures[event] << L" time"; throw Utility::TestWriterException(msg.str()); } } }
// This helper function adds a component to the writer-metadata document
void TestWriter::addComponent(const Component& component, IVssCreateWriterMetadata* pMetadata) { const wchar_t* logicalPath = component.m_logicalPath.empty() ? NULL : component.m_logicalPath.c_str();
// add the component to the document
HRESULT hr = pMetadata->AddComponent(component.m_componentType, // ct
logicalPath, // logicalwszLogicalPath
component.m_name.c_str(), // wszComponentName
NULL, // wszCaption
NULL, // pbIcon
0, // cbIcon
true, // bRestoreMetadata
true, // bNotifyOnBackupComplete
component.m_selectable, // bSelectable
component.m_selectableForRestore // bSelectableForRestore
); checkReturn(hr, L"IVssCreateWriterMetadata::AddComponent");
printStatus(L"\nAdded component: ", Utility::high); printStatus(component.toString(), Utility::high);
// add all of the files to the component. NOTE: we don't allow distinctions between database files
// and database log files in the VSS_CT_DATABASE case.
// we sometimes put a '\' on the end and sometimes not to keep requestors honest.
Component::ComponentFileList::iterator current = component.m_files.begin(); while (current != component.m_files.end()) { if (component.m_componentType == VSS_CT_FILEGROUP) { const wchar_t* alternate = current->m_alternatePath.empty() ? NULL : current->m_alternatePath.c_str(); hr = pMetadata->AddFilesToFileGroup(logicalPath, component.m_name.c_str(), current->m_path.substr(0, current->m_path.size()-1).c_str(), current->m_filespec.c_str(), current->m_recursive, alternate); checkReturn(hr, L"IVssCreateWriterMetadata::AddFilesToFileGroup"); } else if (component.m_componentType == VSS_CT_DATABASE) { hr = pMetadata->AddDatabaseFiles(logicalPath, component.m_name.c_str(), current->m_path.c_str(), current->m_filespec.c_str()); checkReturn(hr, L"IVssCreateWriterMetadata::AddDatabaseFiles"); } else { assert(false); } printStatus(L"\nAdded Component Filespec: "); printStatus(current->toString()); ++current; }
// add all dependencies to the dependency list for the writer
Component::DependencyList::iterator currentDependency = component.m_dependencies.begin(); while (currentDependency != component.m_dependencies.end()) { hr = pMetadata->AddComponentDependency(logicalPath, component.m_name.c_str(), // wszForLogicalPath
currentDependency->m_writerId, // wszForComponentName
currentDependency->m_logicalPath.c_str(), // wszOnLogicalPath
currentDependency->m_componentName.c_str() // wszOnComponentName
); checkReturn(hr, L"IVssCreateWriterMetadata::AddComponentDependency");
printStatus(L"\nAdded Component Dependency: "); printStatus(currentDependency->toString()); ++currentDependency; } }
// This helper function spits all files in a file specification to an alternate location
void TestWriter::spitFiles(const TargetedFile& file) { assert(!file.m_path.empty()); assert(file.m_path[file.m_path.size() - 1] == L'\\'); assert(!file.m_alternatePath.empty()); assert(file.m_alternatePath[file.m_alternatePath.size() - 1] == L'\\');
// ensure that both the source and target directories exist
DWORD attributes = ::GetFileAttributes(file.m_path.c_str()); if ((attributes == INVALID_FILE_ATTRIBUTES) || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) { wstringstream msg; msg << L"The source path " << file.m_path << L" does not exist"; throw Utility::TestWriterException(msg.str()); }
attributes = ::GetFileAttributes(file.m_alternatePath.c_str()); if ((attributes == INVALID_FILE_ATTRIBUTES) || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) { wstringstream msg; msg << L"The target path " << file.m_alternatePath << L" does not exist"; throw Utility::TestWriterException(msg.str()); }
// start by copying files from the specified root directory
std::queue<wstring> paths; paths.push(file.m_path);
// walk through in breadth-first order. It's less resource intensive than depth-first, and
// potentially more performant
while (!paths.empty()) { // --- grab the next path off the queue
wstring currentPath = paths.front(); paths.pop();
// --- start walking all files in the directory
WIN32_FIND_DATA findData; Utility::AutoFindFileHandle findHandle = ::FindFirstFile((currentPath + L'*').c_str(), &findData); if (findHandle == INVALID_HANDLE_VALUE) continue;
do { wstring currentName = findData.cFileName; if (currentName == L"." || currentName == L"..") continue; std::transform(currentName.begin(), currentName.end(), currentName.begin(), towupper);
// --- if we've hit a direcctory and we care to do a recursive spit
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && file.m_recursive) { assert(!currentName.empty()); if (currentName[currentName.size() - 1] != L'\\') currentName += L"\\";
// figure out where the target for this new directory is
assert(currentPath.find(file.m_path) == 0); wstring extraDirectory = currentPath.substr(file.m_path.size()); wstring alternateLocation = file.m_alternatePath + extraDirectory + currentName;
// create a target directory to hold the copied files.
if (!::CreateDirectory(alternateLocation.c_str(), NULL) && ::GetLastError() != ERROR_ALREADY_EXISTS) checkReturn(HRESULT_FROM_WIN32(::GetLastError()), L"CreateDirectory");
m_directoriesToRemove.push(alternateLocation.c_str()); // push the directory on the queue so it gets processed as well
paths.push(currentPath + currentName); continue; }
// --- if we've hit a regular file with a matching filespec
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && wildcardMatches(currentName, file.m_filespec)) { // figure out where the new target location is
assert(currentPath.find(file.m_path) == 0); wstring extraDirectory = currentPath.substr(file.m_path.size()); wstring alternateLocation = file.m_alternatePath + extraDirectory + currentName;
wstringstream msg; msg << L"Spitting File: " << currentPath + currentName << L" To location: " << alternateLocation; printStatus(msg.str() , Utility::high);
// copy the file over
if (!::CopyFile((currentPath + currentName).c_str(), alternateLocation.c_str(), FALSE)) checkReturn(HRESULT_FROM_WIN32(::GetLastError()), L"CopyFile"); else m_toDelete.push_back(alternateLocation); } } while (::FindNextFile(findHandle, &findData)); } }
// extract the component name from an interface pointer
wstring TestWriter::getName(IVssComponent* pComponent) { CComBSTR name; HRESULT hr = pComponent->GetComponentName(&name); checkReturn(hr, L"IVssComponent::GetComponentName");
assert(name != NULL); // this should never happen
return (BSTR)name; }
// extract the component logical path from an interface pointer
wstring TestWriter::getPath(IVssComponent* pComponent) { CComBSTR path; HRESULT hr = pComponent->GetLogicalPath(&path); checkReturn(hr, L"IVssComponent::GetLogicalPath");
// GetLogicalPath can indeed return NULL so be careful
return (path.Length() > 0) ? (BSTR)path : L""; }
// write a backup metadata stamp to the component
void TestWriter::writeBackupMetadata(IVssComponent* pComponent) { HRESULT hr = pComponent->SetBackupMetadata(metadata(pComponent, BackupString).c_str()); checkReturn(hr, L"IVssComponent::SetBackupMetadata");
printStatus(wstring(L"Writing backup metadata: ") + metadata(pComponent, BackupString), Utility::high); }
// verify that a backup metadata stamp is intact
bool TestWriter::verifyBackupMetadata(IVssComponent* pComponent) { CComBSTR data; HRESULT hr = pComponent->GetBackupMetadata(&data); checkReturn(hr, L"IVssComponent::GetBackupMetadata");
printStatus(wstring(L"\nComparing metadata: ") + (data.Length() ? (BSTR)data : L"") + wstring(L" Against expected string: ") + metadata(pComponent, BackupString), Utility::high);
if (data.Length() == 0 || metadata(pComponent, BackupString) != (BSTR)data) return false;
return true; }
// write a restore metadata stamp to the component
void TestWriter::writeRestoreMetadata(IVssComponent* pComponent) { HRESULT hr = pComponent->SetRestoreMetadata(metadata(pComponent, RestoreString).c_str()); checkReturn(hr, L"IVssComponent::SetRestoreMetadata");
printStatus(wstring(L"Writing restore metadata: ") + metadata(pComponent, RestoreString), Utility::high); }
// verify that a restore metadata stamp is intact
bool TestWriter::verifyRestoreMetadata(IVssComponent* pComponent) { CComBSTR data; HRESULT hr = pComponent->GetRestoreMetadata(&data); checkReturn(hr, L"IVssComponent::GetRestoreMetadata");
printStatus(wstring(L"Comparing metadata: ") + (data.Length() ? (BSTR)data : L"") + wstring(L" Against expected string: ") + metadata(pComponent, RestoreString), Utility::high);
if (data.Length() == 0 || metadata(pComponent, RestoreString) != (BSTR)data) return false;
return true; }
// check to see if the specified file (or files) are all in the current snapshot set
// doesn't check directory junctions... this will not be changed anytime soon.
// recursive mount points are also not handled very well
bool TestWriter::checkPathAffected(const TargetedFile& file) { wstring backupPath = file.m_alternatePath.empty() ? file.m_path : file.m_alternatePath; // if the path in question isn't snapshot, then return false
if (!IsPathAffected(backupPath.c_str())) return false;
// if the filespec isn't recursive, then we're done
if (!file.m_recursive) return true;
// get the name of the volume we live on
wchar_t volumeMount[MAX_PATH]; if(!::GetVolumePathName(backupPath.c_str(), volumeMount, MAX_PATH)) checkReturn(HRESULT_FROM_WIN32(::GetLastError()), L"GetVolumePathName"); assert(backupPath.find(volumeMount) == 0); wchar_t volumeName[MAX_PATH]; if (!::GetVolumeNameForVolumeMountPoint(volumeMount, volumeName, MAX_PATH)) checkReturn(HRESULT_FROM_WIN32(::GetLastError()), L"GetVolumeNameForVolumeMountPoint");
// start off with the volume name and starting directory on a worklist.
std::queue<std::pair<wstring, wstring> > worklist; worklist.push(std::make_pair(wstring(volumeName), backupPath.substr(wcslen(volumeMount))));
while (!worklist.empty()) { // get the current volume and directory off the worklist
wstring currentVolume = worklist.front().first; wstring currentPath = worklist.front().second; worklist.pop(); // now, enumerate all mount points on the volume
Utility::AutoFindMountHandle findHandle = ::FindFirstVolumeMountPoint(currentVolume.c_str(), volumeMount, MAX_PATH); if (findHandle == INVALID_HANDLE_VALUE) continue;
do { std::transform(volumeMount, volumeMount + wcslen(volumeMount), volumeMount, towupper); wstring mountPoint = currentVolume + volumeMount; // if this mount point is included in the file specification, the volume better be included in the snapshot set
if ((mountPoint.find(currentVolume + currentPath) == 0) && !IsPathAffected(mountPoint.c_str())) return false;
if (!::GetVolumeNameForVolumeMountPoint(volumeMount, volumeName, MAX_PATH)) checkReturn(HRESULT_FROM_WIN32(::GetLastError()), L"GetVolumeNameForVolumeMountPoint");
// put this volume on the worklist so it gets processed as well
// Mount points always point to the root of a volume, so pass in
// an empty second argument. When junctions are supported, we
// will pass in the target directory as the second argument.
worklist.push(std::make_pair(wstring(volumeName), wstring())); // this line will change when we support junctions
} while (::FindNextVolumeMountPoint(findHandle, volumeMount, MAX_PATH) == TRUE); }
return true; }
// delete all files and directories created in PrepareForSnapshot
void TestWriter::cleanupFiles() { // delete all created files
vector<wstring>::iterator currentFile = m_toDelete.begin(); while (currentFile != m_toDelete.end()) { if (!::DeleteFile(currentFile->c_str())) warnReturn(HRESULT_FROM_WIN32(::GetLastError()), L"DeleteFile");
++currentFile; } m_toDelete.clear();
// remove all created directories in the proper order
while (!m_directoriesToRemove.empty()) { wstring dir = m_directoriesToRemove.top(); if (!::RemoveDirectory(dir.c_str())) warnReturn(HRESULT_FROM_WIN32(::GetLastError()), L"RemoveDirectory");
m_directoriesToRemove.pop(); } }
// check to see if the requestor has added any new targets, and add them to the
// Component structure
void TestWriter::updateNewTargets(IVssComponent* pComponent, Component& writerComponent) { HRESULT hr = S_OK;
UINT newTargetCount = 0; hr = pComponent->GetNewTargetCount(&newTargetCount); checkReturn(hr, L"IVssComponent::GetNewTargetCount"); writerComponent.m_newTargets.clear(); for (UINT x = 0; x < newTargetCount; x++) { // get information about the new target
CComPtr<IVssWMFiledesc> newTarget;
hr = pComponent->GetNewTarget(x, &newTarget); checkReturn(hr, L"IVssComponent::GetNewTarget");
CComBSTR path, filespec, alternateLocation; bool recursive = false;
hr = newTarget->GetPath(&path); checkReturn(hr, L"IVssComponent:GetPath"); hr = newTarget->GetFilespec(&filespec); checkReturn(hr, L"IVssComponent:GetFilespec");
hr = newTarget->GetRecursive(&recursive); checkReturn(hr, L"IVssComponent:GetRecursive");
hr = newTarget->GetAlternateLocation(&alternateLocation); checkReturn(hr, L"IVssComponent:GetAlternateLocation");
// add it to the new-target list
writerComponent.m_newTargets.push_back(TargetedFile(wstring(path), wstring(filespec), recursive, wstring(alternateLocation))); } }
// verify that files in the component were restored properly.
// assumption is that the directory being restored to is empty if the checkExcluded parameter is true.
// currently, we have a very simple-minded approach to handle the wildcard case
// a more general solution will involve hashing files, and will be implemented if time is found
void TestWriter::verifyFilesRestored(IVssComponent* pComponent, const Component& writerComponent) { WriterConfiguration* config = WriterConfiguration::instance();
// no checking is being done. Don't do anything
if (!config->checkIncludes() && !config->checkExcludes()) return;
// for each file in the component
VSS_RESTORE_TARGET target = writerComponent.m_restoreTarget; VSS_RESTOREMETHOD_ENUM method = config->restoreMethod().m_method;
// build the list of all filespecs that need restoring
vector<TargetedFile> componentFiles; buildComponentFiles(writerComponent, std::back_inserter(componentFiles));
for (vector<TargetedFile>::iterator currentFile = componentFiles.begin(); currentFile != componentFiles.end(); ++currentFile) { // --- figure out if there are any matching exclude files
vector<File> excludeFiles; if (config->checkExcludes()) { buildContainer_if(config->excludeFiles().begin(), config->excludeFiles().end(), std::back_inserter(excludeFiles), std::bind2nd(std::ptr_fun(targetMatches), *currentFile)); } // if there's no checking to be done for this filespec, continue
if (excludeFiles.empty() && !config->checkIncludes()) continue;
// --- if there are new targets, look for one that references our file
// --- if we find such a target, ensure that the file was restored there
// --- NOTE: after the interface changes, there should be at most one matching
// --- target.
vector<TargetedFile> targets; buildContainer_if(writerComponent.m_newTargets.begin(), writerComponent.m_newTargets.end(), std::back_inserter(targets), std::bind2nd(std::equal_to<File>(), *currentFile));
if (targets.size() > 1) { wstringstream msg; msg << L"More than one new target matched filespec " << currentFile->toString() << std::endl << L"This is an illegal configuration"; printStatus(msg.str()); }
if (!targets.empty()) { // create a function object to use for verification
VerifyFileAtLocation locationChecker(excludeFiles, pComponent, false);
locationChecker(targets[0], *currentFile); // TODO: no longer need this fancy functor, since we're not doing a for_each
}
vector<TargetedFile> alternateLocations; buildContainer_if(config->restoreMethod().m_alternateLocations.begin(), config->restoreMethod().m_alternateLocations.end(), std::back_inserter(alternateLocations), std::bind2nd(std::equal_to<File>(), *currentFile));
// --- NOTE: once again, interface changes mean we expect at most one
assert(alternateLocations.size() <= 1);
bool alternateRestore = !alternateLocations.empty() && ((target == VSS_RT_ALTERNATE) || (method == VSS_RME_RESTORE_TO_ALTERNATE_LOCATION)); if ((method == VSS_RME_RESTORE_IF_CAN_REPLACE) || (method == VSS_RME_RESTORE_IF_NOT_THERE) || alternateRestore) { // --- in all of these cases, the backup application may restore to an alternate location.
// if we're not in either of the following two states, then the alternate location should only be used if there's
// a matching element in the backup document. Check to see if this is true
// create a function object to use for verification
// TODO: we no longer need this fancy functor object since we're not doing a for_each
if (!alternateLocations.empty()) { VerifyFileAtLocation locationChecker(excludeFiles, pComponent, (target != VSS_RT_ALTERNATE) && (method != VSS_RME_RESTORE_TO_ALTERNATE_LOCATION));
// check to ensure that the file has been restored to each matching alternate location
// once again, this isn't quite correct, but good enough for now. More complicated
// test scenarios will eventually break this.
locationChecker(alternateLocations[0], *currentFile); } }
// none of the above cases are true. We need to check to see that the file is restored to its original location
if ((method != VSS_RME_RESTORE_AT_REBOOT) && (method != VSS_RME_RESTORE_AT_REBOOT_IF_CANNOT_REPLACE) && !alternateRestore) { // create a function object to use for verification
VerifyFileAtLocation locationChecker(excludeFiles, pComponent, false);
locationChecker(TargetedFile(currentFile->m_path, currentFile->m_filespec, currentFile->m_recursive, currentFile->m_path), *currentFile); } } }
bool __cdecl TestWriter::isSubcomponent(ComponentBase sub, ComponentBase super) { // if the components are the same, then return true
if (super == sub) return true;
wstring path = super.m_logicalPath; if (!path.empty() && path[path.size() - 1] != L'\\') path+= L"\\";
path += super.m_name;
// if the supercomponent full path is the same as the subcomponent logical path, then true
if (path == sub.m_logicalPath) return true;
// otherwise, check for partial match
return sub.m_logicalPath.find(path + L"\\") == 0; }
bool __cdecl TestWriter::targetMatches (File target, File file) { assert(!file.m_filespec.empty()); assert(!target.m_filespec.empty()); // the filespec must match first of all
if (!wildcardMatches(file.m_filespec, target.m_filespec)) return false;
// check the path
if (file.m_recursive) { if (!target.m_recursive) return target.m_path.find(file.m_path) == 0; else return (target.m_path.find(file.m_path) == 0) ||(file.m_path.find(target.m_path) == 0); } else { if (!target.m_recursive) return file.m_path == target.m_path; else return file.m_path.find(target.m_path) == 0; } }
// This helper function tests whether a component can be legally added to the backup document
bool __cdecl TestWriter::addableComponent(Component toAdd) { WriterConfiguration* config = WriterConfiguration::instance(); if (toAdd.m_selectable) return true;
// see if there are any selectable ancestors
vector<Component> ancestors; buildContainer_if(config->components().begin(), config->components().end(), std::back_inserter(ancestors), Utility::and1(std::bind2nd(std::ptr_fun(isSupercomponent), toAdd), std::ptr_fun(isComponentSelectable)));
return ancestors.empty(); }
// check to see if two wildcards match.
// specifically, check to see whether the set of expansions of the first wildcard has a
// non-empty intersection with the set of expansions of the second wildcard.
// This function is not terribly efficient, but wildcards tend to be fairly short.
bool TestWriter::wildcardMatches(const wstring& first, const wstring& second) { // if both string are empty, then they surely match
if (first.empty() && second.empty()) return true;
// if we're done with the component, the wildcard better be terminated with '*' characters
if (first.empty()) return (second[0] == L'*') && wildcardMatches(first, second.substr(1)); if (second.empty()) return (first[0] == L'*') && wildcardMatches(first.substr(1), second); switch(first[0]) { case L'?': if (second[0] == L'*') { return wildcardMatches(first.substr(1), second) || // '*' matches character
wildcardMatches(first, second.substr(1)); // '*' matches nothing
}
// otherwise, the rest of the strings must match
return wildcardMatches(first.substr(1), second.substr(1)); case L'*': return wildcardMatches(first, second.substr(1)) || // '*' matches character
wildcardMatches(first.substr(1), second); // '*' matches nothing
default: switch(second[0]) { case L'?': return wildcardMatches(first.substr(1), second.substr(1)); case L'*': return wildcardMatches(first.substr(1), second) || // '*' matches character
wildcardMatches(first, second.substr(1)); // '*' matches nothing
default: return (first[0] == second[0]) && wildcardMatches(first.substr(1), second.substr(1)); } } }
wstring TestWriter::VerifyFileAtLocation::verifyFileAtLocation(const File& file, const TargetedFile& location) const { WriterConfiguration* config = WriterConfiguration::instance();
// complicated set of assertions.
assert(!(file.m_recursive && !location.m_recursive) || (location.m_path.find(file.m_path) == 0)); assert(!(location.m_recursive && !file.m_recursive) || (file.m_path.find(location.m_path) == 0)); assert(!(file.m_recursive && location.m_recursive) || ((file.m_path.find(location.m_path) == 0) || (location.m_path.find(file.m_path) == 0))); assert(!m_excluded.empty() || config->checkIncludes()); assert(m_excluded.empty() || config->checkExcludes()); // performant case where we don't have to walk any directory trees
if (!file.m_recursive && !location.m_recursive && isExact(file.m_filespec)) { assert(m_excluded.size() <= 1); // if not, the config file isn't set up right
// --- if this is an alternate location mapping, only process it if there's a matching alternate location
// --- in the backup document
if (m_verifyAlternateLocation && !verifyAlternateLocation(TargetedFile(file.m_path, file.m_filespec, false, location.m_alternatePath))) { return L""; }
// --- ensure that the file has been restored, unless the file is excluded
printStatus(wstring(L"\nChecking file ") + location.m_alternatePath + file.m_filespec, Utility::high);
// check for error cases
if (m_excluded.empty()) { if (::GetFileAttributes((location.m_alternatePath + file.m_filespec).c_str()) == INVALID_FILE_ATTRIBUTES) { wstringstream msg; msg << L"\nThe file: " << std::endl << file.toString() << std::endl << L"was not restored to location " << location.m_alternatePath; printStatus(msg.str(), Utility::low);
return msg.str(); } } else if (::GetFileAttributes((location.m_alternatePath + file.m_filespec).c_str()) != INVALID_FILE_ATTRIBUTES) { wstringstream msg; msg << L"\nThe file: " << file.m_path << file.m_filespec << L" should have been excluded, but appears in location " << location.m_alternatePath; printStatus(msg.str(), Utility::low);
return msg.str(); }
return L""; }
std::queue<wstring> paths;
// figure out what directory to start looking from
wstring startPath = location.m_alternatePath; if (location.m_recursive && (file.m_path.find(location.m_path) == 0)) startPath += file.m_path.substr(location.m_path.size());
paths.push(startPath);
// in the recursive case, files will hopefully be backed up high in the directory tree
// consequently, we're going to walk the tree breadth-first
printStatus(L"\nChecking that filespec was restored:", Utility::high); while (!paths.empty()) { wstring currentPath = paths.front(); paths.pop();
printStatus(wstring(L" Checking directory: ") + currentPath, Utility::high);
// for every file in the current directory (can't pass in filespec since we want to match all directories)
WIN32_FIND_DATA findData; Utility::AutoFindFileHandle findHandle = ::FindFirstFile((currentPath + L"*").c_str(), &findData); if (findHandle == INVALID_HANDLE_VALUE) continue;
do { wstring currentName = findData.cFileName; std::transform(currentName.begin(), currentName.end(), currentName.begin(), towupper);
if (currentName == L"." || currentName == L"..") continue;
// --- if the file is a directory
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { assert(!currentName.empty()); if (currentName[currentName.size() - 1] != L'\\') currentName += L"\\";
// add it if necessary
if (file.m_recursive) paths.push(currentPath + currentName);
continue; // skip to next file
}
printStatus(wstring(L" Checking file: ") + currentName);
// --- translate the path to what it would have been in the original tree
assert(currentPath.find(location.m_alternatePath) == 0); wstring originalPath = file.m_path; if (file.m_recursive && (location.m_path.find(file.m_path) == 0)) originalPath += location.m_path.substr(file.m_path.size()); originalPath += currentPath.substr(location.m_alternatePath.size());
// --- if this is an alternate location mapping, only process it if there's a matching
// --- alternate location mapping in the backup document
if (m_verifyAlternateLocation && !verifyAlternateLocation(TargetedFile(originalPath, currentName, false, currentPath))) { continue; }
// --- find an exclude item that matches
// --- if !config->checkExcluded(), m_excluded will be an empty container, and
// --- std::find_if will return the end iterator
vector<File>::const_iterator found = std::find_if(m_excluded.begin(), m_excluded.end(), std::bind2nd(std::ptr_fun(targetMatches), File(originalPath, currentName, false)));
// --- return if this is either an excluded file, or if we've found at least one matching include file
if (found != m_excluded.end()) { wstringstream msg; msg << L"The file " << originalPath << currentName << L" should have been excluded, but appears in location " << currentPath; printStatus(msg.str(), Utility::low);
return msg.str(); } else if (config->checkIncludes() && wildcardMatches(currentName, file.m_filespec)) { return L""; // declare success in cheesy case
}
} while (::FindNextFile(findHandle, &findData)); }
if (config->checkIncludes()) { wstringstream msg; msg << L"None of the files specified by " << std::endl << file.toString() << std::endl << L" were restored to location " << location.m_alternatePath; printStatus(msg.str(), Utility::low); return msg.str(); }
// we're only checking excludes, and we didn't find any violations
return L""; }
// verify that an alternate location mapping appears in the backup document
bool TestWriter::VerifyFileAtLocation::verifyAlternateLocation(const TargetedFile& writerAlt) const { assert (isExact(writerAlt.m_filespec)); assert(!writerAlt.m_recursive); unsigned int mappings = 0; HRESULT hr = m_pComponent->GetAlternateLocationMappingCount(&mappings); checkReturn(hr, L"IVssComponent::GetAlternateLocationMappingCount");
for (unsigned int x = 0; x < mappings; x++) { // get the current alternate location mapping
CComPtr<IVssWMFiledesc> filedesc; hr = m_pComponent->GetAlternateLocationMapping(x, &filedesc); checkReturn(hr, L"IVssComponent::GetAlternateLocationMapping");
// grab all relevant fields
CComBSTR bstrPath, bstrFilespec, bstrAlternateLocation;
hr = filedesc->GetPath(&bstrPath); checkReturn(hr, L"IVssComponent::GetPath"); if (bstrPath.Length() == 0) { printStatus(L"An Alternate Location Mapping with an empty path was added to the backup document", Utility::low); continue; } hr = filedesc->GetFilespec(&bstrFilespec); checkReturn(hr, L"IVssComponent::GetFilespec"); if (bstrFilespec.Length() == 0) { printStatus(L"An Alternate Location Mapping with an empty filespec was added to the backup document", Utility::low); continue; }
hr = filedesc->GetAlternateLocation(&bstrAlternateLocation); checkReturn(hr, L"IVssComponent::GetAlternateLocation"); if (bstrAlternateLocation.Length() == 0) { printStatus(L"An Alternate Location Mapping with an empty alternateLocation was added to the backup document", Utility::low); continue; }
bool recursive; hr = filedesc->GetRecursive(&recursive); checkReturn(hr, L"IVssComponent::GetRecursive");
// convert the fields to uppercase and ensure paths are '\' terminated
wstring path = bstrPath; std::transform(path.begin(), path.end(), path.begin(), towupper); if (path[path.size() - 1] != L'\\') path += L'\\'; wstring filespec = bstrFilespec; std::transform(filespec.begin(), filespec.end(), filespec.begin(), towupper);
wstring alternatePath = bstrAlternateLocation; std::transform(alternatePath.begin(), alternatePath.end(), alternatePath.begin(), towupper); if (alternatePath[alternatePath.size() - 1] != L'\\') alternatePath += L'\\';
// check to see if that passed-in mapping is encompassed by the one in the backup document
if (targetMatches(File(path, filespec, recursive), writerAlt)) { if (recursive) { if (writerAlt.m_alternatePath.find(alternatePath) != 0) return false;
assert(writerAlt.m_path.find(path) == 0); alternatePath += writerAlt.m_path.substr(path.size()); }
return alternatePath == writerAlt.m_alternatePath; } }
return false; } // add the current error message to the PostRestoreFailureMsg
void TestWriter::VerifyFileAtLocation::saveErrorMessage(const wstring& message) const { if (!message.empty()) { CComBSTR old; m_pComponent->GetPostRestoreFailureMsg(&old); wstring oldMessage = (old.Length() > 0) ? (BSTR)old : L""; m_pComponent->SetPostRestoreFailureMsg((oldMessage + wstring(L"\n") + message).c_str()); } }
|