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.
1659 lines
41 KiB
1659 lines
41 KiB
// ***************************************************************************
|
|
// Copyright (C) 2000- Microsoft Corporation.
|
|
// @File: snapsql.cpp
|
|
//
|
|
// PURPOSE:
|
|
//
|
|
// Implement the SQLServer Volume Snapshot Writer.
|
|
//
|
|
// NOTES:
|
|
//
|
|
//
|
|
// HISTORY:
|
|
//
|
|
// @Version: Whistler/Yukon
|
|
// 90690 SRS 10/10/01 Minor sqlwriter changes
|
|
// 85581 SRS 08/15/01 Event security
|
|
// 76910 SRS 08/08/01 Rollforward from VSS snapshot
|
|
// 68228 12/05/00 ntsnap work
|
|
// 66601 srs 10/05/00 NTSNAP improvements
|
|
//
|
|
//
|
|
// @EndHeader@
|
|
// ***************************************************************************
|
|
|
|
#if HIDE_WARNINGS
|
|
#pragma warning( disable : 4786)
|
|
#endif
|
|
|
|
#include <stdafx.h>
|
|
|
|
#include <new.h>
|
|
#include "vdierror.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"
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
//----------------------------------------------------------------------
|
|
// Database status bits (from ntdbms/ntinc/database.h)
|
|
//
|
|
const long DBT_CLOSED = 0x2; // database is uninitialized because
|
|
// nobody is in it (see DBT_CLOSE_ON_EXIT)
|
|
const long DBT_NOTREC = 0x40; /* set for each db by recovery before recovering
|
|
any of them */
|
|
const long DBT_INRECOVER = 0x80; /* set by recovery as seach db is recovered */
|
|
const long DBT_CKPT = 0x2000; /* database being checkpointed */
|
|
const long DBT_SHUTDOWN = 0x40000; // database hasn't been bootstrapped yet
|
|
|
|
const long DBT_INLDDB = 0x20; /* set by loaddb - tells recovery not to recover
|
|
this database */
|
|
const long DBT_SUSPECT = 0x100; /* database not recovered successfully */
|
|
const long DBT_DETACHED = 0x80000; // This database has been detached
|
|
const long DBT_STANDBY = 0x200000; // DB is online readonly with RESTORE LOG
|
|
// allowed. This state is set by RECOVERDB.
|
|
|
|
/* set by user - saved in Sysdatabases - moved to DBTABLE at checkpoint */
|
|
const long DBT_CLOSE_ON_EXIT = 0x1; // shutdown if you were last user in
|
|
// this DB
|
|
// WARNING: note that DBT_CLOSED is 0x2
|
|
const long DBT_SELBULK = 0x4;
|
|
const long DBT_AUTOTRUNC = 0x8;
|
|
const long DBT_TORNPAGE = 0x10; // enable torn page detection
|
|
// 0x10 is available (used to be no checkpoint on recovery)
|
|
// WARNING: note that DBT_INLDDB is 0x20
|
|
// WARNING: note that DBT_NOTREC is 0x40
|
|
// WARNING: note that DBT_INRECOVER is 0x80
|
|
// WARNING: note that DBT_SUSPECT is 0x100
|
|
const long DBT_OFFLINE = 0x200; /* database is currently offline */
|
|
const long DBT_RDONLY = 0x400; /* database is read only */
|
|
const long DBT_DBO = 0x800; /* only available to owner, dbcreator of db and sa */
|
|
const long DBT_SINGLE = 0x1000; /* single user only */
|
|
// WARNING: note that DBT_CKPT is 0x2000
|
|
const long DBT_PENDING_UPGRADE = 0x4000; // RESERVED: We are using this bit in Sphinx
|
|
// but not sure if we'll need it in Shiloh.
|
|
// DO NOT take it without consultation.
|
|
const long DBT_USE_NOTREC = 0x8000; /* emergency mode - set to allow db to be not
|
|
recovered but usable */
|
|
// WARNING: note that DBT_SHUTDOWN is 0x40000
|
|
// WARNING: note that DBT_DETACHED is 0x80000
|
|
// WARNING: note that DBT_STANDBY is 0x200000
|
|
const long DBT_AUTOSHRINK = 0x400000; /* autoshrink is enable for the database */
|
|
|
|
// WARNING: in utables 0x8000000 is being added in u_tables.cql to indicate 'table lock on bulk load'
|
|
const long DBT_CLEANLY_SHUTDOWN = 0x40000000; //This database was shutdown in a
|
|
// clean manner with no open
|
|
// transactions and all writes
|
|
// flushed to disk
|
|
|
|
const long DBT_MINIMAL_LOG_IN_DB = 0x10000000; // The database contains pages marked
|
|
// changed due to minimally logged ops.
|
|
const long DBT_MINIMAL_LOG_AFTER_BACKUP = 0x20000000; // The database contains pages marked
|
|
// changed due to
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// Build a literal string from for an identifier.
|
|
// We need to provide database names as strings in some T-SQL contexts.
|
|
// This routine ensures that we handle them all the same way.
|
|
// The output buffer should be SysNameBufferLen in size.
|
|
//
|
|
void
|
|
FormStringForName (WCHAR* pString, const WCHAR* pName)
|
|
{
|
|
pString [0] = 'N'; // unicode prefix
|
|
pString [1] = '\''; // string delimiter
|
|
|
|
UINT ix = 2;
|
|
while (*pName && ix < SysNameBufferLen-3)
|
|
{
|
|
if (*pName == '\'')
|
|
{
|
|
// need to double all quotes
|
|
//
|
|
pString [ix++] = '\'';
|
|
}
|
|
pString [ix++] = *pName;
|
|
pName++;
|
|
}
|
|
|
|
pString [ix++] = '\'';
|
|
pString [ix] = 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// Build a delimited identifier from an identifier.
|
|
// We need to handle special characters in database names via delimited identifiers.
|
|
// This routine ensures that we handle them all the same way.
|
|
// The output buffer should be SysNameBufferLen in size.
|
|
//
|
|
void
|
|
FormDelimitedIdentifier (WCHAR* pString, const WCHAR* pName)
|
|
{
|
|
pString [0] = '['; // unicode prefix
|
|
|
|
UINT ix = 1;
|
|
while (*pName && ix < SysNameBufferLen-3)
|
|
{
|
|
if (*pName == ']')
|
|
{
|
|
// need to double embedded brackets
|
|
//
|
|
pString [ix++] = ']';
|
|
}
|
|
pString [ix++] = *pName;
|
|
pName++;
|
|
}
|
|
|
|
pString [ix++] = ']';
|
|
pString [ix] = 0;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// A handler to call when out of memory.
|
|
//
|
|
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 database properties are retrieved:
|
|
// simple: TRUE if using the simple recovery model.
|
|
// online: TRUE if the database is available for backup usable and currently open
|
|
//
|
|
// This is only intended to work for SQL70 databases.
|
|
//
|
|
void
|
|
FrozenServer::GetDatabaseProperties70 (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".
|
|
//
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormStringForName (stringName, dbName.c_str ());
|
|
WString query =
|
|
L"select status "
|
|
L"from master..sysdatabases where name = " + WString (stringName);
|
|
|
|
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;
|
|
}
|
|
|
|
UINT32 status = (*(UINT32*)m_Connection.AccessColumn (1));
|
|
|
|
if (status & (DBT_INLDDB | DBT_NOTREC | DBT_INRECOVER | DBT_SUSPECT |
|
|
DBT_OFFLINE | DBT_USE_NOTREC | DBT_SHUTDOWN | DBT_DETACHED | DBT_STANDBY))
|
|
{
|
|
*pOnline = FALSE;
|
|
}
|
|
else
|
|
{
|
|
*pOnline = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Build the list of databases to BACKUP for a SQL2000 server for
|
|
// the case of a non-component-based backup.
|
|
//
|
|
// Only volumes are identified.
|
|
// Any database with a file on those volumes is included.
|
|
// "Torn" checking is performed.
|
|
// "Autoclosed" databases are skipped (assume they will stay closed).
|
|
// Only simple recovery is allowed.
|
|
// Skip databases which aren't freezable.
|
|
//
|
|
// 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. It is set to full recovery only
|
|
// to provide defaults for new databases.
|
|
//
|
|
// "Freezable" databases are those in a state suitable for BACKUP.
|
|
// This excludes databases which are not in an full-ONLINE state
|
|
// (due to damage, partially restored, warm standby, etc).
|
|
//
|
|
// UNDONE:
|
|
// In Yukon, the autoclose determination was unstable. Re-check it.
|
|
//
|
|
BOOL
|
|
FrozenServer::FindDatabases2000 (
|
|
CCheckPath* checker)
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"FrozenServer::FindDatabases2000");
|
|
|
|
// Query the databases on this server, looking at properties:
|
|
// (dbname, filename, simpleRecovery, online, inStandby, AutoClose, Autoclosed)
|
|
//
|
|
// We use status bit 0x40000000 (1073741824) to identify
|
|
// clean-shutdown databases which are not really online.
|
|
//
|
|
m_Connection.SetCommand (
|
|
L"select db_name(af.dbid), "
|
|
L"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 1 "
|
|
L"else 0 end, "
|
|
L"convert (int, databasepropertyex(db_name(af.dbid),'IsInStandby')), "
|
|
L"convert (int, databasepropertyex(db_name(af.dbid),'IsAutoClose')), "
|
|
L"case db.status & 1073741824 "
|
|
L"when 1073741824 then 1 "
|
|
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 ();
|
|
|
|
// Results of the query
|
|
//
|
|
WCHAR* pDbName;
|
|
WCHAR* pFileName;
|
|
int* pIsSimple;
|
|
int* pIsOnline;
|
|
int* pIsInStandby;
|
|
int* pIsAutoClose;
|
|
int* pIsClosed;
|
|
|
|
// Track transitions between databases/database files
|
|
//
|
|
bool firstFile = true;
|
|
bool done = false;
|
|
|
|
// Info about the current database being examined
|
|
//
|
|
WString currDbName;
|
|
bool currDbInSnapshot;
|
|
bool currDbIsFreezable;
|
|
bool currDbIsSimple;
|
|
bool currDbIsClosed;
|
|
|
|
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);
|
|
pIsSimple = (int*) m_Connection.AccessColumn (3);
|
|
pIsOnline = (int*) m_Connection.AccessColumn (4);
|
|
pIsInStandby = (int*) m_Connection.AccessColumn (5);
|
|
pIsAutoClose = (int*) m_Connection.AccessColumn (6);
|
|
pIsClosed = (int*) m_Connection.AccessColumn (7);
|
|
|
|
while (!done)
|
|
{
|
|
bool fileInSnap = checker->IsPathInSnapshot (pFileName);
|
|
|
|
// Trace what's happening
|
|
//
|
|
if (firstFile)
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB,
|
|
L"Examining database <%s>\nSimpleRecovery:%d Online:%d Standby:%d AutoClose:%d Closed:%d\n",
|
|
pDbName, *pIsSimple, *pIsOnline, *pIsInStandby, *pIsAutoClose, *pIsClosed);
|
|
}
|
|
ft.Trace(VSSDBG_SQLLIB, L"InSnap(%d): %s\n", (int)fileInSnap, pFileName);
|
|
|
|
if (firstFile)
|
|
{
|
|
firstFile = FALSE;
|
|
|
|
// Remember some facts about this database
|
|
//
|
|
currDbName = WString (pDbName);
|
|
|
|
currDbIsSimple = (*pIsSimple || wcscmp (L"model", pDbName) == 0);
|
|
|
|
currDbIsFreezable = (*pIsOnline && !*pIsInStandby);
|
|
|
|
currDbIsClosed = *pIsAutoClose && *pIsClosed;
|
|
|
|
currDbInSnapshot = fileInSnap;
|
|
|
|
// We can check recovery model and snapshot configuration now
|
|
//
|
|
if (currDbInSnapshot && !currDbIsSimple && currDbIsFreezable)
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASENOTSIMPLE, VSSDBG_SQLLIB << pDbName);
|
|
throw HRESULT (E_SQLLIB_NONSIMPLE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currDbInSnapshot ^ fileInSnap)
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_DATABASEISTORN, VSSDBG_SQLLIB);
|
|
throw HRESULT (E_SQLLIB_TORN_DB);
|
|
}
|
|
}
|
|
|
|
if (!m_Connection.FetchNext ())
|
|
{
|
|
done = true;
|
|
}
|
|
else if (currDbName.compare (pDbName))
|
|
{
|
|
firstFile = TRUE;
|
|
}
|
|
|
|
if (done || firstFile)
|
|
{
|
|
// To be part of the BACKUP, the database must:
|
|
// - be covered by the snapshot
|
|
// - in a freezeable state
|
|
//
|
|
// IsSimpleOnly implicitly selects all open databases.
|
|
// Non-open databases are also part of the volume snapshot,
|
|
// but there is no need to freeze them, since they aren't
|
|
// changing.
|
|
//
|
|
if (currDbInSnapshot && currDbIsFreezable && !currDbIsClosed)
|
|
{
|
|
m_FrozenDatabases.push_back (currDbName);
|
|
}
|
|
}
|
|
}
|
|
|
|
return m_FrozenDatabases.size () > 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Determine if there are databases which qualify for a freeze on this server.
|
|
// Returns TRUE if so.
|
|
//
|
|
// Processing varies based on the type of snapshot:
|
|
// 1) ComponentBased
|
|
// The requestor explicitly identifies databases of interest.
|
|
// All recovery models are allowed.
|
|
// "Torn" checking isn't performed (database filenames are irrelevant)
|
|
//
|
|
// 2) NonComponentBased
|
|
// 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 (checker->IsComponentBased ())
|
|
{
|
|
PCWSTR dbName;
|
|
UINT nextIndex = 0;
|
|
while (dbName = checker->EnumerateSelectedDatabases (m_Name.c_str (), &nextIndex))
|
|
{
|
|
m_FrozenDatabases.push_back (WString (dbName));
|
|
}
|
|
return m_FrozenDatabases.size () > 0;
|
|
}
|
|
|
|
// Handle non-component-based snapshot
|
|
//
|
|
|
|
|
|
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++)
|
|
{
|
|
// 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.
|
|
//
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormDelimitedIdentifier (stringName, (*i).c_str ());
|
|
WString command = L"select rtrim(filename) from "
|
|
+ WString (stringName) + 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;
|
|
GetDatabaseProperties70 (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++)
|
|
{
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormDelimitedIdentifier (stringName, (*i).c_str ());
|
|
command += L"use " + WString (stringName) + 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++)
|
|
{
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormStringForName (stringName, (*i).c_str ());
|
|
command += L"dbcc freeze_io (" + WString (stringName) + 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++)
|
|
{
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormStringForName (stringName, (*i).c_str ());
|
|
command += L"dbcc thaw_io (" + WString (stringName) + L")\n";
|
|
}
|
|
|
|
m_Connection.SetCommand (command);
|
|
m_Connection.ExecCommand ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void
|
|
FrozenServer::GetDatabaseInfo (UINT dbIndex, FrozenDatabaseInfo* pInfo)
|
|
{
|
|
FrozenDatabase* pDb = &m_pFreeze2000->m_pDBContext [dbIndex];
|
|
|
|
pInfo->serverName = m_Name.c_str ();
|
|
pInfo->databaseName = pDb->m_DbName.c_str ();
|
|
//pInfo->isSimpleRecovery = pDb->m_IsSimpleModel;
|
|
pInfo->pMetaData = pDb->m_MetaData.GetImage (&pInfo->metaDataSize);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 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;
|
|
|
|
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 (UINT i=0; i < servers->size (); i++)
|
|
{
|
|
FrozenServer* p = new FrozenServer ((*servers)[i]);
|
|
|
|
m_FrozenServers.push_back (p);
|
|
}
|
|
}
|
|
|
|
// 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 ();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// Fetch info about the first interesting database
|
|
//
|
|
HRESULT
|
|
Snapshot::GetFirstDatabase (
|
|
FrozenDatabaseInfo* pInfo) throw ()
|
|
{
|
|
m_DbIndex = 0;
|
|
m_ServerIter = m_FrozenServers.begin ();
|
|
|
|
return GetNextDatabase (pInfo);
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// We don't return any info about SQL7 databases since
|
|
// the purpose here is to retrieve VDI metadata needed for
|
|
// BACKUP/RESTORE WITH SNAPSHOT.
|
|
//
|
|
HRESULT
|
|
Snapshot::GetNextDatabase (
|
|
FrozenDatabaseInfo* pInfo) throw ()
|
|
{
|
|
while (m_ServerIter != m_FrozenServers.end ())
|
|
{
|
|
FrozenServer* pSrv = *m_ServerIter;
|
|
|
|
if (pSrv->m_pFreeze2000 &&
|
|
m_DbIndex < pSrv->m_pFreeze2000->m_NumDatabases)
|
|
{
|
|
pSrv->GetDatabaseInfo (m_DbIndex, pInfo);
|
|
m_DbIndex++;
|
|
return NOERROR;
|
|
}
|
|
|
|
m_DbIndex = 0;
|
|
m_ServerIter++;
|
|
}
|
|
return DB_S_ENDOFROWSET;
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
// 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 the SQL end of the snapshot.
|
|
//
|
|
CSqlRestore*
|
|
CreateSqlRestore () throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"CreateSqlRestore");
|
|
try
|
|
{
|
|
return new RestoreHandler;
|
|
}
|
|
catch (...)
|
|
{
|
|
ft.Trace(VSSDBG_SQLLIB, L"Out of memory");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
RestoreHandler::RestoreHandler ()
|
|
{
|
|
// This GUID is used as the name for the VDSet
|
|
// We can reuse it for multiple restores, since only one
|
|
// will run at a time.
|
|
//
|
|
CoCreateGuid (&m_VDSId);
|
|
}
|
|
|
|
|
|
|
|
// Inform SQLServer that data laydown is desired on the full database.
|
|
// Performs a DETACH, preventing SQLServer from touching the files.
|
|
//
|
|
HRESULT
|
|
RestoreHandler::PrepareToRestore (
|
|
const WCHAR* pInstance,
|
|
const WCHAR* pDatabase)
|
|
throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"RestoreHandler::PrepareToRestore");
|
|
|
|
TRY_SQLLIB
|
|
{
|
|
m_Connection.Connect (pInstance);
|
|
WCHAR stringName[SysNameBufferLen];
|
|
|
|
FormStringForName (stringName, pDatabase);
|
|
|
|
WString command =
|
|
L"if exists (select name from sysdatabases where name=" +
|
|
WString(stringName) +
|
|
L") ALTER DATABASE ";
|
|
FormDelimitedIdentifier (stringName, pDatabase);
|
|
command += WString (stringName) + L" SET OFFLINE WITH ROLLBACK IMMEDIATE";
|
|
|
|
m_Connection.SetCommand (command);
|
|
m_Connection.ExecCommand ();
|
|
}
|
|
END_SQLLIB
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------
|
|
// Map the voids and proc call stuff to the real
|
|
// thread routine.
|
|
//
|
|
DWORD WINAPI RestoreVDProc(
|
|
LPVOID lpParameter ) // thread data
|
|
{
|
|
((RestoreHandler*)lpParameter)->RestoreVD ();
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Feed the MD back to SQLServer
|
|
// Our caller setup the VDS, but we've got to finish the open processing.
|
|
//
|
|
void
|
|
RestoreHandler::RestoreVD ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"RestoreHandler::RestoreVD");
|
|
|
|
VDC_Command * cmd;
|
|
DWORD completionCode;
|
|
DWORD bytesTransferred;
|
|
HRESULT hr;
|
|
const BYTE *pCurData = m_pMetaData;
|
|
VDConfig config;
|
|
|
|
hr = m_pIVDSet->GetConfiguration (INFINITE, &config);
|
|
if (FAILED (hr))
|
|
{
|
|
ft.Trace (VSSDBG_SQLLIB, L"Unexpected GetConfiguration hr: x%X\n", hr);
|
|
m_pIVDSet->SignalAbort ();
|
|
return;
|
|
}
|
|
|
|
hr = m_pIVDSet->OpenDevice (m_SetName, &m_pIVD);
|
|
if (FAILED (hr))
|
|
{
|
|
ft.Trace (VSSDBG_SQLLIB, L"Unexpected OpenDevice hr: x%X\n", hr);
|
|
m_pIVDSet->SignalAbort ();
|
|
return;
|
|
}
|
|
|
|
while (SUCCEEDED (hr=m_pIVD->GetCommand (INFINITE, &cmd)))
|
|
{
|
|
bytesTransferred = 0;
|
|
switch (cmd->commandCode)
|
|
{
|
|
case VDC_Read:
|
|
if (pCurData+cmd->size > m_pMetaData+m_MetaDataSize)
|
|
{
|
|
// attempting to read past end of data.
|
|
//
|
|
completionCode = ERROR_HANDLE_EOF;
|
|
}
|
|
else
|
|
{
|
|
memcpy (cmd->buffer, pCurData, cmd->size);
|
|
pCurData+= cmd->size;
|
|
bytesTransferred = cmd->size;
|
|
}
|
|
|
|
case VDC_ClearError:
|
|
completionCode = ERROR_SUCCESS;
|
|
break;
|
|
|
|
case VDC_MountSnapshot:
|
|
// There is nothing to do here, since the snapshot
|
|
// is already mounted.
|
|
//
|
|
completionCode = ERROR_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
// If command is unknown...
|
|
completionCode = ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
hr = m_pIVD->CompleteCommand (cmd, completionCode, bytesTransferred, 0);
|
|
if (!SUCCEEDED (hr))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hr == VD_E_CLOSE)
|
|
{
|
|
ft.hr = NOERROR;
|
|
}
|
|
else
|
|
{
|
|
ft.Trace (VSSDBG_SQLLIB, L"Unexpected VD termination: x%X\n", hr);
|
|
ft.hr = hr;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// After data is laid down, this performs RESTORE WITH SNAPSHOT[,NORECOVERY]
|
|
//
|
|
HRESULT
|
|
RestoreHandler::FinalizeRestore (
|
|
const WCHAR* pInstance,
|
|
const WCHAR* pDatabase,
|
|
bool compositeRestore, // true if WITH NORECOVERY desired
|
|
const BYTE* pMetadata, // metadata obtained from BACKUP
|
|
unsigned int dataLen) // size of metadata (in bytes)
|
|
throw ()
|
|
{
|
|
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"RestoreHandler::FinalizeRestore");
|
|
|
|
if (!MetaData::IsValidImage (pMetadata, dataLen))
|
|
{
|
|
// Brian, do we want to log something here?
|
|
// This shouldn't happen, but I add a csum just to be sure....
|
|
//
|
|
ft.Trace (VSSDBG_SQLLIB, L"Bad metadata for database %s\\%s", pInstance, pDatabase);
|
|
return E_SQLLIB_GENERIC;
|
|
}
|
|
|
|
m_pIVDSet = NULL;
|
|
m_pIVD = NULL;
|
|
m_pMetaData = pMetadata;
|
|
m_MetaDataSize = dataLen - sizeof(UINT); // chop off the checksum
|
|
m_hThread = NULL;
|
|
|
|
TRY_SQLLIB
|
|
{
|
|
// Make sure we have a connection to the server
|
|
//
|
|
m_Connection.Connect (pInstance);
|
|
|
|
// Build a VDS for the RESTORE
|
|
//
|
|
|
|
#ifdef TESTDRV
|
|
ft.hr = CoCreateInstance (
|
|
CLSID_MSSQL_ClientVirtualDeviceSet,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IClientVirtualDeviceSet2,
|
|
(void**)&m_pIVDSet);
|
|
#else
|
|
ft.CoCreateInstanceWithLog (
|
|
VSSDBG_SQLLIB,
|
|
CLSID_MSSQL_ClientVirtualDeviceSet,
|
|
L"MSSQL_ClientVirtualDeviceSet",
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IClientVirtualDeviceSet2,
|
|
(IUnknown**)&(m_pIVDSet));
|
|
#endif
|
|
|
|
if (ft.HrFailed())
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_CANTCREATEVDS, VSSDBG_SQLLIB << ft.hr);
|
|
ft.Throw
|
|
(
|
|
VSSDBG_SQLLIB,
|
|
ft.hr,
|
|
L"Failed to create VDS object. hr = 0x%08lx",
|
|
ft.hr
|
|
);
|
|
}
|
|
|
|
VDConfig config;
|
|
memset (&config, 0, sizeof(config));
|
|
config.deviceCount = 1;
|
|
|
|
StringFromGUID2 (m_VDSId, m_SetName, sizeof (m_SetName)/sizeof(WCHAR));
|
|
|
|
// A "\" indicates a named instance; we need the "raw" instance name
|
|
//
|
|
WCHAR* pShortInstance = wcschr (pInstance, L'\\');
|
|
|
|
if (pShortInstance)
|
|
{
|
|
pShortInstance++; // step over the separator
|
|
}
|
|
|
|
// Create the virtual device set
|
|
//
|
|
ft.hr = m_pIVDSet->CreateEx (pShortInstance, m_SetName, &config);
|
|
if (ft.HrFailed())
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_CANTCREATEVDS, VSSDBG_SQLLIB << ft.hr);
|
|
ft.Throw
|
|
(
|
|
VSSDBG_SQLLIB,
|
|
ft.hr,
|
|
L"Failed to create VDS object. hr = 0x%08lx",
|
|
ft.hr
|
|
);
|
|
}
|
|
|
|
// Spawn a thread to feed the VD metadata....
|
|
//
|
|
m_hThread = CreateThread (NULL, 0,
|
|
RestoreVDProc, this, 0, NULL);
|
|
|
|
if (m_hThread == NULL)
|
|
{
|
|
ft.hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ft.CheckForError(VSSDBG_SQLLIB, L"CreateThread");
|
|
}
|
|
|
|
// Send the RESTORE command, which will cause the VD metadata
|
|
// to be consumed.
|
|
//
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormDelimitedIdentifier (stringName, pDatabase);
|
|
|
|
WString command =
|
|
L"RESTORE DATABASE " + WString(stringName) + L" FROM VIRTUAL_DEVICE='" +
|
|
m_SetName + L"' WITH SNAPSHOT,BUFFERCOUNT=1,BLOCKSIZE=1024";
|
|
if (compositeRestore)
|
|
{
|
|
command += L",NORECOVERY";
|
|
}
|
|
|
|
m_Connection.SetCommand (command);
|
|
m_Connection.ExecCommand ();
|
|
|
|
// Unless an exception is thrown, we were sucessful.
|
|
//
|
|
ft.hr = NOERROR;
|
|
}
|
|
END_SQLLIB
|
|
|
|
if (m_pIVDSet)
|
|
{
|
|
// If we hit an error, we'll need to clean up
|
|
//
|
|
if (ft.hr != NOERROR)
|
|
{
|
|
m_pIVDSet->SignalAbort ();
|
|
}
|
|
|
|
if (m_hThread)
|
|
{
|
|
// We gotta wait for our thread, since it's using our resources.
|
|
//
|
|
DWORD status = WaitForSingleObjectEx (m_hThread, INFINITE, TRUE);
|
|
if (status != WAIT_OBJECT_0)
|
|
{
|
|
ft.Trace (VSSDBG_SQLLIB, L"Unexpected thread-wait status: x%x", status);
|
|
}
|
|
CloseHandle (m_hThread);
|
|
}
|
|
m_pIVDSet->Close ();
|
|
m_pIVDSet->Release ();
|
|
}
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 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 = true;
|
|
|
|
// Bummer, the enumeration is just a list of strings.....
|
|
//pSrv->supportsCompositeRestore = true;
|
|
|
|
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 = true;
|
|
|
|
//pSrv->supportsCompositeRestore = true;
|
|
|
|
ft.hr = NOERROR;
|
|
}
|
|
}
|
|
END_SQLLIB
|
|
}
|
|
|
|
return ft.hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Copy out the info from the result set
|
|
//
|
|
void
|
|
SqlEnumerator::SetupDatabaseInfo (DatabaseInfo* pDbInfo)
|
|
{
|
|
WCHAR *pDbName = (WCHAR*)m_Connection.AccessColumn (1);
|
|
UINT status = *(int*)m_Connection.AccessColumn (2);
|
|
|
|
wcscpy (pDbInfo->name, pDbName);
|
|
|
|
pDbInfo->isSimpleRecovery = (DBT_AUTOTRUNC & status) ? true : false;
|
|
pDbInfo->status = status;
|
|
|
|
pDbInfo->supportsFreeze = false;
|
|
if (wcscmp (pDbName, L"tempdb") != 0)
|
|
{
|
|
// Databases not fully online are not eligible for backup.
|
|
//
|
|
if (!(status & (DBT_INLDDB | DBT_NOTREC | DBT_INRECOVER | DBT_SUSPECT |
|
|
DBT_OFFLINE | DBT_USE_NOTREC | DBT_SHUTDOWN | DBT_DETACHED | DBT_STANDBY)))
|
|
{
|
|
pDbInfo->supportsFreeze = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 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,convert (int,status) from master.dbo.sysdatabases");
|
|
m_Connection.ExecCommand ();
|
|
|
|
if (!m_Connection.FetchFirst ())
|
|
{
|
|
ft.LogError(VSS_ERROR_SQLLIB_NORESULTFORSYSDB, VSSDBG_SQLLIB);
|
|
THROW_GENERIC;
|
|
}
|
|
|
|
SetupDatabaseInfo (pDbInfo);
|
|
|
|
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
|
|
{
|
|
SetupDatabaseInfo (pDbInfo);
|
|
|
|
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)
|
|
{
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormStringForName (stringName, pDbName);
|
|
|
|
query = L"select rtrim(filename),status & 64 from sysaltfiles where DB_ID("
|
|
+ WString(stringName) + L") = dbid";
|
|
}
|
|
else
|
|
{
|
|
WCHAR stringName[SysNameBufferLen];
|
|
FormDelimitedIdentifier (stringName, pDbName);
|
|
|
|
query = L"select rtrim(filename),status & 64 from "
|
|
+ WString(stringName) + 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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Provide a simple container for BACKUP metadata.
|
|
//
|
|
MetaData::MetaData ()
|
|
{
|
|
m_UsedLength = 0;
|
|
m_AllocatedLength = 0x2000; // 8K will represent any small database
|
|
m_pData = new BYTE [m_AllocatedLength];
|
|
}
|
|
MetaData::~MetaData ()
|
|
{
|
|
if (m_pData)
|
|
{
|
|
delete[] m_pData;
|
|
}
|
|
}
|
|
void
|
|
MetaData::Append (const BYTE* pData, UINT length)
|
|
{
|
|
// We don't need to handle misalignment for the csum.
|
|
//
|
|
DBG_ASSERT (length % sizeof(UINT) == 0);
|
|
|
|
if (m_UsedLength + length > m_AllocatedLength)
|
|
{
|
|
BYTE* pNew = new BYTE [m_AllocatedLength*2];
|
|
memcpy (pNew, m_pData, m_UsedLength);
|
|
delete[] m_pData;
|
|
m_pData = pNew;
|
|
m_AllocatedLength *= 2;
|
|
}
|
|
memcpy (m_pData+m_UsedLength, pData, length);
|
|
m_UsedLength += length;
|
|
}
|
|
void
|
|
MetaData::Finalize ()
|
|
{
|
|
UINT csum = Checksum (m_pData, m_UsedLength);
|
|
Append ((BYTE*)&csum, sizeof(csum));
|
|
}
|
|
const BYTE*
|
|
MetaData::GetImage (UINT *pLength)
|
|
{
|
|
*pLength = m_UsedLength;
|
|
return m_pData;
|
|
}
|
|
BOOL
|
|
MetaData::IsValidImage (const BYTE* pData, UINT length)
|
|
{
|
|
return (0 == Checksum (pData, length));
|
|
}
|
|
UINT
|
|
MetaData::Checksum (const BYTE* pData, UINT length)
|
|
{
|
|
UINT csum = 0;
|
|
UINT nwords = length/sizeof(csum);
|
|
UINT* pWord = (UINT*)pData;
|
|
while (nwords>0)
|
|
{
|
|
csum ^= *pWord;
|
|
pWord++;
|
|
nwords--;
|
|
}
|
|
return csum;
|
|
}
|