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