You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1065 lines
24 KiB
1065 lines
24 KiB
// ***************************************************************************
|
|
// Copyright (C) 2000- Microsoft Corporation.
|
|
// @File: snapsql.cpp
|
|
//
|
|
// PURPOSE:
|
|
//
|
|
// Implement the SQLServer Volume Snapshot Writer.
|
|
//
|
|
// NOTES:
|
|
//
|
|
//
|
|
// HISTORY:
|
|
//
|
|
// @Version: Whistler/Shiloh
|
|
// 66601 srs 10/05/00 NTSNAP improvements
|
|
//
|
|
//
|
|
// @EndHeader@
|
|
// ***************************************************************************
|
|
|
|
#if HIDE_WARNINGS
|
|
#pragma warning( disable : 4786)
|
|
#endif
|
|
|
|
#include <stdafx.h>
|
|
|
|
#include <new.h>
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// 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 "SQLSNAPC"
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
int __cdecl out_of_store(size_t size)
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"out_of_store");
|
|
ft.Trace(VSSDBG_SQLLIB, L"out of memory");
|
|
|
|
throw HRESULT (E_OUTOFMEMORY);
|
|
return 0;
|
|
}
|
|
|
|
class AutoNewHandler
|
|
{
|
|
public:
|
|
AutoNewHandler ()
|
|
{
|
|
m_oldHandler = _set_new_handler (out_of_store);
|
|
}
|
|
|
|
~AutoNewHandler ()
|
|
{
|
|
_set_new_handler (m_oldHandler);
|
|
}
|
|
|
|
private:
|
|
_PNH m_oldHandler;
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Handle enviroment stuff:
|
|
// - tracing/error logging
|
|
// - mem alloc
|
|
//
|
|
IMalloc * g_pIMalloc = NULL;
|
|
|
|
HRESULT
|
|
InitSQLEnvironment()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"InitSqlEnvironment");
|
|
try
|
|
{
|
|
ft.hr = CoGetMalloc(1, &g_pIMalloc);
|
|
if (ft.HrFailed())
|
|
ft.Trace(VSSDBG_SQLLIB, L"Failed to get task allocator: hr=0x%X", ft.hr);
|
|
}
|
|
catch (...)
|
|
{
|
|
ft.hr = E_SQLLIB_GENERIC;
|
|
}
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Return TRUE if the server is online and a connection shouldn't take forever!
|
|
//
|
|
BOOL
|
|
IsServerOnline (const WCHAR* serverName)
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"IsServerOnline");
|
|
|
|
WCHAR eventName [300];
|
|
WCHAR* pInstance;
|
|
|
|
wcscpy (eventName, L"Global\\sqlserverRecComplete");
|
|
|
|
// A "\" indicates a named instance, so append the name...
|
|
//
|
|
pInstance = wcschr (serverName, L'\\');
|
|
|
|
if (pInstance)
|
|
{
|
|
wcscat (eventName, L"$");
|
|
wcscat (eventName, pInstance+1);
|
|
}
|
|
|
|
HANDLE hEvent = CreateEventW (NULL, TRUE, FALSE, eventName);
|
|
|
|
if (hEvent == NULL)
|
|
{
|
|
ft.hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ft.LogError(VSS_ERROR_SQLLIB_CANT_CREATE_EVENT, VSSDBG_SQLLIB << ft.hr);
|
|
THROW_GENERIC;
|
|
}
|
|
|
|
// If the event isn't signaled, the server is not up.
|
|
//
|
|
BOOL result = (WaitForSingleObject (hEvent, 0) == WAIT_OBJECT_0);
|
|
|
|
CloseHandle (hEvent);
|
|
|
|
return result;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Return TRUE if the database properties are retrieved:
|
|
// simple: TRUE if using the simple recovery model.
|
|
// online: TRUE if the database is usable and currently open
|
|
//
|
|
void
|
|
FrozenServer::GetDatabaseProperties (const WString& dbName,
|
|
BOOL* pSimple,
|
|
BOOL* pOnline)
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::GetDatabaseProperties");
|
|
|
|
// We use status bit 0x40000000 (1073741824) to identify
|
|
// clean-shutdown databases which are "offline".
|
|
//
|
|
WString query =
|
|
L"select databaseproperty(N'" + dbName + L"','IsTruncLog'),"
|
|
L"case status & 1073741824 "
|
|
L"when 1073741824 then 0 "
|
|
L"else 1 end "
|
|
L"from master..sysdatabases where name = N'" + dbName + L"'";
|
|
|
|
m_Connection.SetCommand (query);
|
|
m_Connection.ExecCommand ();
|
|
|
|
if (!m_Connection.FetchFirst ())
|
|
{
|
|
LPCWSTR wsz = dbName.c_str();
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASE_NOT_IN_SYSDATABASES, VSSDBG_SQLLIB << wsz);
|
|
THROW_GENERIC;
|
|
}
|
|
|
|
*pSimple = (BOOL)(*(int*)m_Connection.AccessColumn (1));
|
|
*pOnline = (BOOL)(*(int*)m_Connection.AccessColumn (2));
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Called only by "FindDatabasesToFreeze" to implement a smart access strategy:
|
|
// - use sysaltfiles to qualify the databases.
|
|
// This avoids access to shutdown or damaged databases.
|
|
//
|
|
// Autoclose databases which are not started are left out of the freeze-list.
|
|
// We do this to avoid scaling problems, especially on desktop systems.
|
|
// However, such db's are still evaluated to see if they are "torn".
|
|
//
|
|
// The 'model' database is allowed to be a full recovery database, since only
|
|
// database backups are sensible for it.
|
|
//
|
|
BOOL
|
|
FrozenServer::FindDatabases2000 (
|
|
CCheckPath* checker)
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::FindDatabases2000");
|
|
|
|
// Create an ordered set of tuples (dbname, filename, simpleRecovery, dbIsActive)
|
|
//
|
|
// We use status bit 0x40000000 (1073741824) to identify
|
|
// clean-shutdown databases which are not active.
|
|
//
|
|
m_Connection.SetCommand (
|
|
L"select db_name(af.dbid),rtrim(af.filename), "
|
|
L"case databasepropertyex(db_name(af.dbid),'recovery') "
|
|
L"when 'SIMPLE' then 1 "
|
|
L"else 0 end,"
|
|
L"case databasepropertyex(db_name(af.dbid),'Status') "
|
|
L"when 'ONLINE' then case db.status & 1073741824 "
|
|
L"when 1073741824 then 0 "
|
|
L"else 1 end "
|
|
L"else 0 end "
|
|
L"from master..sysaltfiles af, master..sysdatabases db "
|
|
L"where af.dbid = db.dbid and af.dbid != db_id('tempdb') "
|
|
L"order by af.dbid"
|
|
);
|
|
|
|
m_Connection.ExecCommand ();
|
|
|
|
WCHAR* pDbName;
|
|
WCHAR* pFileName;
|
|
int* pSimple;
|
|
int* pIsOnline;
|
|
WString currentDbName;
|
|
BOOL firstDb = TRUE;
|
|
BOOL firstFile;
|
|
BOOL shouldFreeze = FALSE;
|
|
BOOL masterLast = FALSE;
|
|
BOOL currDbIsOnline;
|
|
|
|
if (!m_Connection.FetchFirst ())
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_SYSALTFILESEMPTY, VSSDBG_SQLLIB);
|
|
THROW_GENERIC;
|
|
}
|
|
|
|
pDbName = (WCHAR*)m_Connection.AccessColumn (1);
|
|
pFileName = (WCHAR*)m_Connection.AccessColumn (2);
|
|
pSimple = (int*)m_Connection.AccessColumn (3);
|
|
pIsOnline = (int*)m_Connection.AccessColumn (4);
|
|
|
|
while (1)
|
|
{
|
|
|
|
// Check out the current row
|
|
//
|
|
BOOL fileInSnap = checker->IsPathInSnapshot (pFileName);
|
|
if (fileInSnap && !*pSimple && wcscmp (L"model", pDbName))
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASENOTSIMPLE, VSSDBG_SQLLIB << pDbName);
|
|
throw HRESULT (E_SQLLIB_NONSIMPLE);
|
|
}
|
|
|
|
// Is this the next database?
|
|
//
|
|
if (firstDb || currentDbName.compare (pDbName))
|
|
{
|
|
if (!firstDb)
|
|
{
|
|
// Deal with completed database
|
|
//
|
|
if (shouldFreeze && currDbIsOnline)
|
|
{
|
|
if (currentDbName == L"master")
|
|
{
|
|
masterLast = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_FrozenDatabases.push_back (currentDbName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep info about the newly encountered db
|
|
//
|
|
currentDbName = WString (pDbName);
|
|
currDbIsOnline = *pIsOnline;
|
|
firstFile = TRUE;
|
|
firstDb = FALSE;
|
|
ft.Trace(VSSDBG_SQLLIB, L"Examining %s. SimpleRecovery:%d Online:%d\n", pDbName, *pSimple, *pIsOnline);
|
|
}
|
|
|
|
ft.Trace(VSSDBG_SQLLIB, L"%s\n", pFileName);
|
|
|
|
if (firstFile)
|
|
{
|
|
shouldFreeze = fileInSnap;
|
|
firstFile = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (shouldFreeze ^ fileInSnap)
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASEISTORN, VSSDBG_SQLLIB);
|
|
throw HRESULT (E_SQLLIB_TORN_DB);
|
|
}
|
|
}
|
|
|
|
if (!m_Connection.FetchNext ())
|
|
{
|
|
// Deal with the current database
|
|
//
|
|
if (shouldFreeze && currDbIsOnline)
|
|
{
|
|
m_FrozenDatabases.push_back (currentDbName);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (masterLast)
|
|
{
|
|
m_FrozenDatabases.push_back (L"master");
|
|
}
|
|
|
|
return m_FrozenDatabases.size () > 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Determine if there are databases which qualify for a freeze on this server.
|
|
// Returns TRUE if so.
|
|
// Throws if any qualified databases are any of:
|
|
// - "torn" (not fully covered by the snapshot)
|
|
// - hosted by a server which can't support freeze
|
|
// - are not "simple" databases
|
|
//
|
|
BOOL
|
|
FrozenServer::FindDatabasesToFreeze (
|
|
CCheckPath* checker)
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::FindDatabasesToFreeze");
|
|
|
|
m_Connection.Connect (m_Name);
|
|
|
|
m_FrozenDatabases.clear ();
|
|
|
|
if (m_Connection.GetServerVersion () > 7)
|
|
{
|
|
// SQL2000 allows us to use a better access strategy.
|
|
//
|
|
return FindDatabases2000 (checker);
|
|
}
|
|
|
|
m_Connection.SetCommand (L"select name from sysdatabases where name != 'tempdb'");
|
|
m_Connection.ExecCommand ();
|
|
std::auto_ptr<StringVector> dbList (m_Connection.GetStringColumn ());
|
|
BOOL masterLast = FALSE;
|
|
|
|
for (StringVectorIter i = dbList->begin (); i != dbList->end (); i++)
|
|
{
|
|
// UNDONE: SKIP OVER DB'S in SHUTDOWN DB'S?
|
|
// DB'S IN LOAD, ETC?
|
|
// We'll avoid freezing shutdown db's, but we don't avoid
|
|
// enumerating their files (they might be torn)
|
|
//
|
|
|
|
// Note the [] around the dbname to handle non-trivial dbnames.
|
|
//
|
|
WString command = L"select rtrim(filename) from [";
|
|
command += *i + L"]..sysfiles";
|
|
|
|
m_Connection.SetCommand (command);
|
|
try
|
|
{
|
|
m_Connection.ExecCommand ();
|
|
}
|
|
catch (...)
|
|
{
|
|
// We've decided to be optimistic:
|
|
// If we can't get the list of files, ignore this database.
|
|
//
|
|
ft.Trace(VSSDBG_SQLLIB, L"Failed to get db files for %s\n", i->c_str ());
|
|
|
|
continue;
|
|
}
|
|
|
|
std::auto_ptr<StringVector> fileList (m_Connection.GetStringColumn ());
|
|
|
|
BOOL first=TRUE;
|
|
BOOL shouldFreeze;
|
|
|
|
for (StringVectorIter iFile = fileList->begin ();
|
|
iFile != fileList->end (); iFile++)
|
|
{
|
|
BOOL fileInSnap = checker->IsPathInSnapshot (iFile->c_str ());
|
|
|
|
if (first)
|
|
{
|
|
shouldFreeze = fileInSnap;
|
|
}
|
|
else
|
|
{
|
|
if (shouldFreeze ^ fileInSnap)
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASEISTORN, VSSDBG_SQLLIB << i->c_str());
|
|
throw HRESULT (E_SQLLIB_TORN_DB);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shouldFreeze)
|
|
{
|
|
BOOL simple, online;
|
|
GetDatabaseProperties (i->c_str (), &simple, &online);
|
|
if (!simple && L"model" != *i)
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASENOTSIMPLE, VSSDBG_SQLLIB << i->c_str ());
|
|
throw HRESULT (E_SQLLIB_NONSIMPLE);
|
|
}
|
|
if (online)
|
|
{
|
|
if (L"master" == *i)
|
|
{
|
|
masterLast = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_FrozenDatabases.push_back (*i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (masterLast)
|
|
{
|
|
m_FrozenDatabases.push_back (L"master");
|
|
}
|
|
|
|
|
|
return m_FrozenDatabases.size () > 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Prep the server for the freeze.
|
|
// For SQL2000, start a BACKUP WITH SNAPSHOT.
|
|
// For SQL7, issuing checkpoints to each database.
|
|
// This minimizes the recovery processing needed when the snapshot is restored.
|
|
//
|
|
BOOL
|
|
FrozenServer::Prepare ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::Prepare");
|
|
|
|
if (m_Connection.GetServerVersion () > 7)
|
|
{
|
|
m_pFreeze2000 = new Freeze2000 (m_Name, m_FrozenDatabases.size ());
|
|
|
|
// Release the connection, we won't need it anymore
|
|
//
|
|
m_Connection.Disconnect ();
|
|
|
|
for (StringListIter i=m_FrozenDatabases.begin ();
|
|
i != m_FrozenDatabases.end (); i++)
|
|
{
|
|
m_pFreeze2000->PrepareDatabase (*i);
|
|
}
|
|
m_pFreeze2000->WaitForPrepare ();
|
|
}
|
|
else
|
|
{
|
|
WString command;
|
|
|
|
for (StringListIter i=m_FrozenDatabases.begin ();
|
|
i != m_FrozenDatabases.end (); i++)
|
|
{
|
|
command += L"use [" + *i + L"]\ncheckpoint\n";
|
|
}
|
|
|
|
m_Connection.SetCommand (command);
|
|
m_Connection.ExecCommand ();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// Freeze the server by issuing freeze commands
|
|
// to each database.
|
|
// Returns an exception if any failure occurs.
|
|
//
|
|
BOOL
|
|
FrozenServer::Freeze ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::Freeze");
|
|
if (m_pFreeze2000)
|
|
{
|
|
m_pFreeze2000->Freeze ();
|
|
}
|
|
else
|
|
{
|
|
WString command;
|
|
|
|
for (StringListIter i=m_FrozenDatabases.begin ();
|
|
i != m_FrozenDatabases.end (); i++)
|
|
{
|
|
command += L"dbcc freeze_io (N'" + *i + L"')\n";
|
|
}
|
|
|
|
m_Connection.SetCommand (command);
|
|
m_Connection.ExecCommand ();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// Thaw the server by issuing thaw commands
|
|
// to each database.
|
|
// For SQL7, we can't tell if the database was
|
|
// already thawn.
|
|
// But for SQL2000, we'll return TRUE only if
|
|
// the databases were still all frozen at the thaw time.
|
|
BOOL
|
|
FrozenServer::Thaw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::Thaw");
|
|
if (m_pFreeze2000)
|
|
{
|
|
return m_pFreeze2000->Thaw ();
|
|
}
|
|
|
|
WString command;
|
|
|
|
for (StringListIter i=m_FrozenDatabases.begin ();
|
|
i != m_FrozenDatabases.end (); i++)
|
|
{
|
|
command += L"dbcc thaw_io (N'" + *i + L"')\n";
|
|
}
|
|
|
|
m_Connection.SetCommand (command);
|
|
m_Connection.ExecCommand ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Create an object to handle the SQL end of the snapshot.
|
|
//
|
|
CSqlSnapshot*
|
|
CreateSqlSnapshot () throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"CreateSqlSnapshot");
|
|
try
|
|
{
|
|
return new Snapshot;
|
|
}
|
|
catch (...)
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"Out of memory");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Move to an uninitialized state.
|
|
//
|
|
void
|
|
Snapshot::Deinitialize ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Snapshot::Deinitialize");
|
|
|
|
if (m_Status == Frozen)
|
|
{
|
|
Thaw ();
|
|
}
|
|
for (ServerIter i=m_FrozenServers.begin ();
|
|
i != m_FrozenServers.end (); i++)
|
|
{
|
|
delete *i;
|
|
}
|
|
m_FrozenServers.clear ();
|
|
m_Status = NotInitialized;
|
|
}
|
|
|
|
Snapshot::~Snapshot ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Snapshot::~Snapshot");
|
|
|
|
try
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"\n~CSqlSnapshot called\n");
|
|
Deinitialize ();
|
|
}
|
|
catch (...)
|
|
{
|
|
// swallow!
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
// Prepare for the snapshot:
|
|
// - identify the installed servers
|
|
// - for each server that is "up":
|
|
// - identify databases affected by the snapshot
|
|
// - if there are such databases, fail the snapshot if:
|
|
// - the server doesn't support snapshots
|
|
// - the database isn't a SIMPLE database
|
|
// - the database is "torn" (not all files in the snapshot)
|
|
//
|
|
//
|
|
HRESULT
|
|
Snapshot::Prepare (CCheckPath* checker) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Snapshot::Prepare");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
try
|
|
{
|
|
AutoNewHandler t;
|
|
|
|
// hack in a test of the new handler
|
|
//
|
|
#if 0
|
|
while (1)
|
|
{
|
|
char*p = new char [100000];
|
|
if (p==NULL)
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"Can never happen!\n");
|
|
THROW_GENERIC;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (m_Status != NotInitialized)
|
|
{
|
|
Deinitialize ();
|
|
}
|
|
|
|
// The state moves immediately to enumerated, indicating
|
|
// that the frozen server list may be non-empty.
|
|
//
|
|
m_Status = Enumerated;
|
|
|
|
// Build a list of servers on this machine.
|
|
//
|
|
{
|
|
std::auto_ptr<StringVector> servers (EnumerateServers ());
|
|
|
|
// Scan over the servers, picking out the online ones.
|
|
//
|
|
for (int i=0; i < servers->size (); i++)
|
|
{
|
|
if (IsServerOnline ((*servers)[i].c_str ()))
|
|
{
|
|
FrozenServer* p = new FrozenServer ((*servers)[i]);
|
|
|
|
m_FrozenServers.push_back (p);
|
|
}
|
|
else
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"Server %s is not online\n", (*servers)[i].c_str ());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Evaulate the server databases to find those which need to freeze.
|
|
//
|
|
ServerIter i=m_FrozenServers.begin ();
|
|
while (i != m_FrozenServers.end ())
|
|
{
|
|
if (!(**i).FindDatabasesToFreeze (checker))
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"Server %s has no databases to freeze\n", ((**i).GetName ()).c_str ());
|
|
|
|
// Forget about this server, it's got nothing to do.
|
|
//
|
|
delete *i;
|
|
i = m_FrozenServers.erase (i);
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// Prep the servers for the freeze
|
|
//
|
|
for (i=m_FrozenServers.begin (); i != m_FrozenServers.end (); i++)
|
|
{
|
|
(*i)->Prepare ();
|
|
}
|
|
|
|
#if 0
|
|
// debug: print the frozen list
|
|
//
|
|
for (i=m_FrozenServers.begin ();
|
|
i != m_FrozenServers.end (); i++)
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"FrozenServer: %s\n", ((**i).GetName ()).c_str ());
|
|
}
|
|
#endif
|
|
|
|
m_Status = Prepared;
|
|
}
|
|
catch (HRESULT& e)
|
|
{
|
|
hr = e;
|
|
}
|
|
catch (...)
|
|
{
|
|
hr = E_SQLLIB_GENERIC;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
// Freeze any prepared servers
|
|
//
|
|
HRESULT
|
|
Snapshot::Freeze () throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Snapshot::Freeze");
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_Status != Prepared)
|
|
{
|
|
return E_SQLLIB_PROTO;
|
|
}
|
|
|
|
try
|
|
{
|
|
AutoNewHandler t;
|
|
|
|
// If any server is frozen, we are frozen.
|
|
//
|
|
m_Status = Frozen;
|
|
|
|
// Ask the servers to freeze
|
|
//
|
|
for (ServerIter i=m_FrozenServers.begin (); i != m_FrozenServers.end (); i++)
|
|
{
|
|
(*i)->Freeze ();
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
hr = E_SQLLIB_GENERIC;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// Thaw all the servers.
|
|
// This routine must not throw. It's safe in a destructor
|
|
//
|
|
// DISCUSS WITH BRIAN....WE MUST RETURN "SUCCESS" only if the
|
|
// servers were all still frozen. Otherwise the snapshot must
|
|
// have been cancelled.
|
|
//
|
|
HRESULT
|
|
Snapshot::Thaw () throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Snapshot::Thaw");
|
|
HRESULT hr = S_OK;
|
|
AutoNewHandler t;
|
|
|
|
// Ask the servers to thaw
|
|
//
|
|
for (ServerIter i=m_FrozenServers.begin (); i != m_FrozenServers.end (); i++)
|
|
{
|
|
try
|
|
{
|
|
if (!(*i)->Thaw ())
|
|
{
|
|
hr = E_SQLLIB_GENERIC;
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
hr = E_SQLLIB_GENERIC;
|
|
ft.LogError(VSS_ERROR_SQLLIB_ERRORTHAWSERVER, VSSDBG_SQLLIB << ((**i).GetName ()).c_str ());
|
|
}
|
|
}
|
|
|
|
// We still have the original list of servers.
|
|
// The snapshot object is reusable if another "Prepare" is done, which will
|
|
// re-enumerate the servers.
|
|
//
|
|
m_Status = Enumerated;
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Setup some try/catch/handlers for our interface...
|
|
// The invoker defines "hr" which is set if an exception
|
|
// occurs.
|
|
//
|
|
#define TRY_SQLLIB \
|
|
try {\
|
|
AutoNewHandler _myNewHandler;
|
|
|
|
#define END_SQLLIB \
|
|
} catch (HRESULT& e)\
|
|
{\
|
|
ft.hr = e;\
|
|
}\
|
|
catch (...)\
|
|
{\
|
|
ft.hr = E_SQLLIB_GENERIC;\
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Create an object to handle enumerations
|
|
//
|
|
CSqlEnumerator*
|
|
CreateSqlEnumerator () throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"CreateSqlEnumerator");
|
|
try
|
|
{
|
|
return new SqlEnumerator;
|
|
}
|
|
catch (...)
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"Out of memory");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
SqlEnumerator::~SqlEnumerator ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::~SqlEnumerator");
|
|
|
|
if (m_pServers)
|
|
delete m_pServers;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Begin retrieval of the servers.
|
|
//
|
|
HRESULT
|
|
SqlEnumerator::FirstServer (ServerInfo* pSrv) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::FirstServer");
|
|
|
|
|
|
if (m_pServers)
|
|
{
|
|
delete m_pServers;
|
|
m_pServers = NULL;
|
|
}
|
|
|
|
m_CurrServer = 0;
|
|
|
|
TRY_SQLLIB
|
|
{
|
|
m_pServers = EnumerateServers ();
|
|
|
|
if (m_pServers->size () == 0)
|
|
{
|
|
ft.hr = DB_S_ENDOFROWSET;
|
|
}
|
|
else
|
|
{
|
|
wcscpy (pSrv->name, (*m_pServers)[0].c_str ());
|
|
pSrv->isOnline = IsServerOnline (pSrv->name) ? true : false;
|
|
|
|
m_CurrServer = 1;
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
}
|
|
END_SQLLIB
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Continue retrieval of the servers.
|
|
//
|
|
HRESULT
|
|
SqlEnumerator::NextServer (ServerInfo* pSrv) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::NextServer");
|
|
|
|
|
|
if (!m_pServers)
|
|
{
|
|
ft.hr = E_SQLLIB_PROTO;
|
|
}
|
|
else
|
|
{
|
|
TRY_SQLLIB
|
|
{
|
|
if (m_CurrServer >= m_pServers->size ())
|
|
{
|
|
ft.hr = DB_S_ENDOFROWSET;
|
|
}
|
|
else
|
|
{
|
|
wcscpy (pSrv->name, (*m_pServers)[m_CurrServer].c_str ());
|
|
m_CurrServer++;
|
|
|
|
pSrv->isOnline = IsServerOnline (pSrv->name) ? true : false;
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
}
|
|
END_SQLLIB
|
|
}
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Begin retrieval of the databases
|
|
//
|
|
HRESULT
|
|
SqlEnumerator::FirstDatabase (const WCHAR *pServerName, DatabaseInfo* pDbInfo) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::FirstDatabase");
|
|
|
|
TRY_SQLLIB
|
|
{
|
|
m_Connection.Connect (pServerName);
|
|
m_Connection.SetCommand (
|
|
L"select name,DATABASEPROPERTY(name,'IsTruncLog') from master..sysdatabases");
|
|
m_Connection.ExecCommand ();
|
|
|
|
if (!m_Connection.FetchFirst ())
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_NORESULTFORSYSDB, VSSDBG_SQLLIB);
|
|
THROW_GENERIC;
|
|
}
|
|
|
|
WCHAR *pDbName = (WCHAR*)m_Connection.AccessColumn (1);
|
|
int* pSimple = (int*)m_Connection.AccessColumn (2);
|
|
|
|
wcscpy (pDbInfo->name, pDbName);
|
|
pDbInfo->supportsFreeze = *pSimple &&
|
|
m_Connection.GetServerVersion () >= 7;
|
|
|
|
m_State = DatabaseQueryActive;
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
END_SQLLIB
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Continue retrieval of the databases
|
|
//
|
|
HRESULT
|
|
SqlEnumerator::NextDatabase (DatabaseInfo* pDbInfo) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::NextDatabase");
|
|
|
|
if (m_State != DatabaseQueryActive)
|
|
{
|
|
ft.hr = E_SQLLIB_PROTO;
|
|
}
|
|
else
|
|
{
|
|
TRY_SQLLIB
|
|
{
|
|
if (!m_Connection.FetchNext ())
|
|
{
|
|
ft.hr = DB_S_ENDOFROWSET;
|
|
}
|
|
else
|
|
{
|
|
WCHAR* pDbName = (WCHAR*)m_Connection.AccessColumn (1);
|
|
int* pSimple = (int*)m_Connection.AccessColumn (2);
|
|
|
|
wcscpy (pDbInfo->name, pDbName);
|
|
pDbInfo->supportsFreeze = *pSimple &&
|
|
m_Connection.GetServerVersion () >= 7;
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
}
|
|
END_SQLLIB
|
|
}
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Begin retrieval of the database files
|
|
//
|
|
HRESULT
|
|
SqlEnumerator::FirstFile (
|
|
const WCHAR* pServerName,
|
|
const WCHAR* pDbName,
|
|
DatabaseFileInfo* pFileInfo) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::FirstFile");
|
|
|
|
TRY_SQLLIB
|
|
{
|
|
m_Connection.Connect (pServerName);
|
|
|
|
WString query;
|
|
|
|
if (m_Connection.GetServerVersion () >= 8)
|
|
{
|
|
query = L"select rtrim(filename),status & 64 from sysaltfiles where DB_ID('"
|
|
+ WString(pDbName) + L"') = dbid";
|
|
}
|
|
else
|
|
{
|
|
query = L"select rtrim(filename),status & 64 from ["
|
|
+ WString(pDbName) + L"]..sysfiles";
|
|
}
|
|
|
|
m_Connection.SetCommand (query);
|
|
m_Connection.ExecCommand ();
|
|
|
|
if (!m_Connection.FetchFirst ())
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_NORESULTFORSYSDB, VSSDBG_SQLLIB);
|
|
THROW_GENERIC;
|
|
}
|
|
|
|
WCHAR* pName = (WCHAR*)m_Connection.AccessColumn (1);
|
|
int* pLogFile = (int*)m_Connection.AccessColumn (2);
|
|
|
|
wcscpy (pFileInfo->name, pName);
|
|
pFileInfo->isLogFile = (*pLogFile != 0);
|
|
|
|
m_State = FileQueryActive;
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
END_SQLLIB
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Continue retrieval of the files
|
|
//
|
|
HRESULT
|
|
SqlEnumerator::NextFile (DatabaseFileInfo* pFileInfo) throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"SqlEnumerator::NextFile");
|
|
|
|
if (m_State != FileQueryActive)
|
|
{
|
|
ft.hr = E_SQLLIB_PROTO;
|
|
}
|
|
else
|
|
{
|
|
TRY_SQLLIB
|
|
{
|
|
if (!m_Connection.FetchNext ())
|
|
{
|
|
ft.hr = DB_S_ENDOFROWSET;
|
|
}
|
|
else
|
|
{
|
|
WCHAR* pName = (WCHAR*)m_Connection.AccessColumn (1);
|
|
int* pLogFile = (int*)m_Connection.AccessColumn (2);
|
|
|
|
wcscpy (pFileInfo->name, pName);
|
|
pFileInfo->isLogFile = (*pLogFile != 0);
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
}
|
|
END_SQLLIB
|
|
}
|
|
|
|
return ft.hr;
|
|
}
|
|
|