// *************************************************************************** // 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 #include #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 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 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 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; }