// =========================================================================== // File: DBGLOG.CXX // CDLDebugLog: Class for Debug Logs // DebugLogElement: Node class for debug log messages // #include #include extern HINSTANCE g_hInst; // Initialize static variables CList CDLDebugLog::s_dlogList; TCHAR CDLDebugLog::s_szMessage[MAX_DEBUG_STRING_LENGTH]; CMutexSem CDLDebugLog::s_mxsDLogList; BOOL CDLDebugLog::s_bMessage(FALSE); CMutexSem CDLDebugLog::s_mxsMessage; CDLDebugLog::CDLDebugLog() :m_DebugLogList(), m_fAddedDebugLogHead(FALSE), m_iRefCount(0) { m_szFileName[0] = '\0'; m_szUrlName[0] = '\0'; m_szMainClsid[0] = '\0'; m_szMainType[0] = '\0'; m_szMainExt[0] = '\0'; m_szMainUrl[0] = '\0'; } CDLDebugLog::~CDLDebugLog() { Clear(); } int CDLDebugLog::AddRef() { return ++m_iRefCount; } int CDLDebugLog::Release() { ASSERT(m_iRefCount > 0); m_iRefCount--; if(m_iRefCount <= 0) { delete this; return 0; } else return m_iRefCount; } CDLDebugLog * CDLDebugLog::MakeDebugLog() { return new CDLDebugLog(); } // Delete all new'd data and start a new log void CDLDebugLog::Clear() { DebugLogElement *pMsg = NULL; LISTPOSITION pos; int iNumMessages; int i; // delete new'd messages iNumMessages = m_DebugLogList.GetCount(); pos = m_DebugLogList.GetHeadPosition(); for (i = 0; i < iNumMessages; i++) { pMsg = m_DebugLogList.GetNext(pos); delete pMsg; } m_DebugLogList.RemoveAll(); m_fAddedDebugLogHead = FALSE; // commit any lingering CacheEntry if(m_szUrlName[0]) { FILETIME ftExpireTime; FILETIME ftTime; GetSystemTimeAsFileTime(&ftTime); ftExpireTime.dwLowDateTime = (DWORD)0; ftExpireTime.dwHighDateTime = (DWORD)0; CommitUrlCacheEntry(m_szUrlName, m_szFileName, ftExpireTime, ftTime, NORMAL_CACHE_ENTRY, NULL, 0, NULL, 0); } m_szUrlName[0] = 0; m_szFileName[0] = 0; } // Initializes the main clsid, type, ext, and codebase of the debuglog from the // corresponding values in the CCodeDownload // If any of the CCodeDownload's parameters are NULL, they are ignored // (An assertion is thrown if they are all NULL) // for Retail: returns false if they are all NULL BOOL CDLDebugLog::Init(CCodeDownload * pcdl) { int iRes = 0; if(pcdl->GetMainDistUnit()) { iRes = WideCharToMultiByte(CP_ACP, 0, pcdl->GetMainDistUnit(), -1, m_szMainClsid, MAX_DEBUG_STRING_LENGTH, NULL, NULL); ASSERT(iRes != 0); } if(pcdl->GetMainType()) { iRes = WideCharToMultiByte(CP_ACP, 0, pcdl->GetMainType(), -1, m_szMainType, MAX_DEBUG_STRING_LENGTH, NULL, NULL); ASSERT(iRes != 0); } if(pcdl->GetMainExt()) { iRes = WideCharToMultiByte(CP_ACP, 0, pcdl->GetMainExt(), -1, m_szMainExt, MAX_DEBUG_STRING_LENGTH, NULL, NULL); ASSERT(iRes != 0); } if(pcdl->GetMainURL()) { iRes = WideCharToMultiByte(CP_ACP, 0, pcdl->GetMainURL(), -1, m_szMainUrl, INTERNET_MAX_URL_LENGTH, NULL, NULL); ASSERT(iRes != 0); } ASSERT(iRes != 0); return(iRes != 0); } // Initializes the main clsid, type, ext, and codebase of the debuglog // If any of the parameters are NULL, they are ignored // (An assertion is thrown if they are all NULL) // for Retail: returns false if they are all NULL BOOL CDLDebugLog::Init(LPCWSTR wszMainClsid, LPCWSTR wszMainType, LPCWSTR wszMainExt, LPCWSTR wszMainUrl) { int iRes = 0; if(wszMainClsid) { iRes = WideCharToMultiByte(CP_ACP, 0, wszMainClsid, -1, m_szMainClsid, MAX_DEBUG_STRING_LENGTH, NULL, NULL); ASSERT(iRes != 0); } if(wszMainType) { iRes = WideCharToMultiByte(CP_ACP, 0, wszMainType, -1, m_szMainType, MAX_DEBUG_STRING_LENGTH, NULL, NULL); ASSERT(iRes != 0); } if(wszMainExt) { WideCharToMultiByte(CP_ACP, 0, wszMainExt, -1, m_szMainExt, MAX_DEBUG_STRING_LENGTH, NULL, NULL); ASSERT(iRes != 0); } if(wszMainUrl) { WideCharToMultiByte(CP_ACP, 0, wszMainUrl, -1, m_szMainUrl, INTERNET_MAX_URL_LENGTH, NULL, NULL); ASSERT(iRes != 0); } ASSERT(iRes != 0); return(iRes != 0); } // Make the cache file for the debug log using the current main clsid, type, ext, // and url data. The previous values in m_szUrlName and m_szFileName are cleaned // up and written over. void CDLDebugLog::MakeFile() { TCHAR szExtension[] = TEXT("HTM"); if(m_szFileName[0]) { return; // Only make the file if we don't already have one } m_szUrlName[0] = 0; m_szFileName[0] = 0; if(m_szMainClsid[0]) { wnsprintf(m_szUrlName, sizeof(m_szUrlName)-1, "?CodeDownloadErrorLog!name=%s", m_szMainClsid); } else if(m_szMainType[0]) { wnsprintf(m_szUrlName, sizeof(m_szUrlName)-1, "?CodeDownloadErrorLog!type=%s", m_szMainType); } else if(m_szMainExt[0]) { wnsprintf(m_szUrlName, sizeof(m_szUrlName)-1, "?CodeDownloadErrorLog!ext=%s", m_szMainExt); } else { wnsprintf(m_szUrlName, sizeof(m_szUrlName)-1, "?CodeDownloadErrorLog!"); } CreateUrlCacheEntry(m_szUrlName, 0, szExtension, m_szFileName, 0); } // --------------------------------------------------------------------------- // %%Function: CDLDebugLog::DebugOut(int iOption, const char *pscFormat, ...) // Replacement for UrlMkDebugOut() and CCodeDownload::CodeDownloadDebugOut // calls to log code download debug/error messages // --------------------------------------------------------------------------- void CDLDebugLog::DebugOut(int iOption, BOOL fOperationFailed, UINT iResId, ...) { // Temp solution to prevent buffer overruns in debug logging code. // Long term, the printfs should be constrained. It will be a must // if URLs become fully dynamic. static char szDebugString[MAX_DEBUG_STRING_LENGTH*5]; static char szFormatString[MAX_DEBUG_FORMAT_STRING_LENGTH]; va_list args; LoadString(g_hInst, iResId, szFormatString, MAX_DEBUG_FORMAT_STRING_LENGTH); va_start(args, iResId); vsprintf(szDebugString, szFormatString, args); va_end(args); DebugOutPreFormatted(iOption, fOperationFailed, szDebugString); } // Debug out taken a preformatted string instead of a resid format address and // an arbitrary list of arguments. szDebugString is the string which will be outputted // as the debug statement void CDLDebugLog::DebugOutPreFormatted(int iOption, BOOL fOperationFailed, LPTSTR szDebugString) { static TCHAR szUrlMkDebugOutString[MAX_DEBUG_STRING_LENGTH]; DebugLogElement *pdbglog = NULL; DebugLogElement *pdbglogHead = NULL; pdbglog = new DebugLogElement(szDebugString); if(! pdbglog) return; wnsprintf(szUrlMkDebugOutString, MAX_DEBUG_STRING_LENGTH-1, "CODE DL:%s", szDebugString); UrlMkDebugOut((iOption,szUrlMkDebugOutString)); if (pdbglog != NULL) { m_DebugLogList.AddTail(pdbglog); if (fOperationFailed && !m_fAddedDebugLogHead) { m_fAddedDebugLogHead = TRUE; pdbglogHead = new DebugLogElement("--- Detailed Error Log Follows ---\n"); if(! pdbglogHead) return; m_DebugLogList.AddHead(pdbglogHead); pdbglogHead = new DebugLogElement(*pdbglog); if(! pdbglogHead) return; m_DebugLogList.AddHead(pdbglogHead); } } } #define DEBUG_LOG_HTML_START TEXT("
\n")
#define DEBUG_LOG_HTML_END              TEXT("\n
") #define PAD_DIGITS_FOR_STRING(x) (((x) > 9) ? TEXT("") : TEXT("0")) // --------------------------------------------------------------------------- // %%Function: CDLDebugLog::DumpDebugLog() // Output the debug error log. This log is written as a cache entry. // pszCacheFileName is an outparam for the name of the file in the cache, // cbBufLen is the length of the buffer // szErrorMsg is the error message causing the dump, and hrError the hr causing // the dump // --------------------------------------------------------------------------- void CDLDebugLog::DumpDebugLog(LPTSTR pszCacheFileName, int cbBufLen, LPTSTR szErrorMsg, HRESULT hrError) { DebugLogElement *pMsg = NULL; LPCSTR pszStr = NULL; LISTPOSITION pos = NULL; int iNumMessages = 0; int i = 0; HANDLE hFile = INVALID_HANDLE_VALUE; FILETIME ftTime; FILETIME ftExpireTime; DWORD dwBytes = 0; SYSTEMTIME systime; static TCHAR pszHeader[MAX_DEBUG_STRING_LENGTH]; static const TCHAR *ppszMonths[] = {TEXT("Jan"), TEXT("Feb"), TEXT("Mar"), TEXT("Apr"), TEXT("May"), TEXT("Jun"), TEXT("Jul"), TEXT("Aug"), TEXT("Sep"), TEXT("Oct"), TEXT("Nov"), TEXT("Dec")}; // Get the filename and put it in the out LPTSTR if(!m_szFileName[0]) MakeFile(); if(pszCacheFileName) lstrcpyn(pszCacheFileName,m_szFileName, cbBufLen); iNumMessages = m_DebugLogList.GetCount(); pos = m_DebugLogList.GetHeadPosition(); if (pos) { pMsg = m_DebugLogList.GetAt(pos); if (pMsg != NULL) { hFile = CreateFile(m_szFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { // Write the header WriteFile(hFile, DEBUG_LOG_HTML_START, strlen(DEBUG_LOG_HTML_START), &dwBytes, NULL); GetLocalTime(&systime); wnsprintf(pszHeader, ARRAY_ELEMENTS(pszHeader)-1, "*** Code Download Log entry (%s%d %s %d @ %s%d:%s%d:%s%d) ***\n", PAD_DIGITS_FOR_STRING(systime.wDay), systime.wDay, ppszMonths[systime.wMonth - 1], systime.wYear, PAD_DIGITS_FOR_STRING(systime.wHour), systime.wHour, PAD_DIGITS_FOR_STRING(systime.wMinute), systime.wMinute, PAD_DIGITS_FOR_STRING(systime.wSecond), systime.wSecond); WriteFile(hFile, pszHeader, strlen(pszHeader), &dwBytes, NULL); // Write what error caused this dump wnsprintf(pszHeader, ARRAY_ELEMENTS(pszHeader)-1, "Code Download Error: (hr = %lx) %s\n", hrError, (szErrorMsg == NULL) ? ("(null)") : (szErrorMsg)); WriteFile(hFile, pszHeader, strlen(pszHeader), &dwBytes, NULL); wnsprintf(pszHeader, ARRAY_ELEMENTS(pszHeader)-1, "Operation failed. Detailed Information:\n" " CodeBase: %s\n" " CLSID: %s\n" " Extension: %s\n" " Type: %s\n\n", m_szMainUrl, m_szMainClsid, m_szMainExt, m_szMainType); WriteFile(hFile, pszHeader, strlen(pszHeader), &dwBytes, NULL); // Write the Debug Log pos = m_DebugLogList.GetHeadPosition(); iNumMessages = m_DebugLogList.GetCount(); for (i = 0; i < iNumMessages; i++) { pMsg = m_DebugLogList.GetNext(pos); pszStr = pMsg->GetLogMessage(); WriteFile(hFile, pszStr, strlen(pszStr), &dwBytes, NULL); } // Close and clean WriteFile(hFile, DEBUG_LOG_HTML_END, strlen(DEBUG_LOG_HTML_END), &dwBytes, NULL); CloseHandle(hFile); GetSystemTimeAsFileTime(&ftTime); ftExpireTime.dwLowDateTime = (DWORD)0; ftExpireTime.dwHighDateTime = (DWORD)0; CommitUrlCacheEntry(m_szUrlName, m_szFileName, ftExpireTime, ftTime, NORMAL_CACHE_ENTRY, NULL, 0, NULL, 0); m_fAddedDebugLogHead = FALSE; m_szUrlName[0] = NULL; m_szFileName[0] = NULL; } } } } // returns TRUE if there is already a message saved // (If there is a message saved already, it will not be written over // unless bOverwrite is true) BOOL CDLDebugLog::SetSavedMessage(LPCTSTR szMessage, BOOL bOverwrite) { CLock lck(s_mxsMessage); BOOL bRet = FALSE; if(s_bMessage) bRet = TRUE; if((!s_bMessage) || bOverwrite) { lstrcpyn(s_szMessage, szMessage, MAX_DEBUG_STRING_LENGTH); s_bMessage=TRUE; } return bRet; } LPCTSTR CDLDebugLog::GetSavedMessage() { CLock lck(s_mxsMessage); if(!s_bMessage) // Make sure to return an empty string if one has not been set { s_szMessage[0] = '\0'; } return s_szMessage; } // add a debug log to the global list void CDLDebugLog::AddDebugLog(CDLDebugLog * dlog) { CLock lck(s_mxsDLogList); if(dlog) s_dlogList.AddTail(dlog); } // Remove a given debug log from the global list void CDLDebugLog::RemoveDebugLog(CDLDebugLog * dlog) { CLock lck(s_mxsDLogList); if(dlog) { POSITION pos = s_dlogList.Find(dlog); if(pos != NULL) s_dlogList.RemoveAt(pos); } } // Gets the debug log with the given clsid, mime type, file extension or url code base. // Naming priority is the same as at debug file urlname creation: clsid, then type, then ext, then url // The names are checked in this order (so two logs with the same extension but different classids will never // be confused, since clsids are checked before extensions) // If an earlier name is NULL or doesn't produce a match, the next name is checked. // If all names fails to produce a match, the return value is NULL CDLDebugLog * CDLDebugLog::GetDebugLog(LPCWSTR wszMainClsid, LPCWSTR wszMainType, LPCWSTR wszMainExt, LPCWSTR wszMainUrl) { TCHAR szComparer[MAX_DEBUG_STRING_LENGTH]; POSITION pos = NULL; szComparer[0] = NULL; CDLDebugLog * dlogCur = NULL; const int iSwitchMax = 4; LPCWSTR ppwszMains[] = {wszMainClsid, wszMainType, wszMainExt, wszMainUrl}; int iSwitch = iSwitchMax; int iRes = 0; // Find the first non-empty name for(iSwitch = 0; iSwitch < iSwitchMax; iSwitch++) { if((ppwszMains[iSwitch]) && (ppwszMains[iSwitch])[0]) break; } if(iSwitch >= iSwitchMax) return NULL; // Grab mutex for accesing the list CLock lck(s_mxsDLogList); // Loop over the names; a higher array value means a lower priority name; // don't check a lower priority name unless all higher priority names have // failed while(iSwitch < iSwitchMax) { iRes = WideCharToMultiByte(CP_ACP, 0, ppwszMains[iSwitch], -1, szComparer, MAX_DEBUG_STRING_LENGTH, NULL, NULL); if(iRes == 0) return NULL; // Look through the entire list for a debuglog whose name (clsid, type, ext, or url) // matches the given one for(pos = s_dlogList.GetHeadPosition(); pos != NULL; ) { dlogCur = s_dlogList.GetNext(pos); switch(iSwitch) { case 0: if(!lstrcmp(szComparer, dlogCur->GetMainClsid())) return dlogCur; break; case 1: if(!lstrcmp(szComparer, dlogCur->GetMainType())) return dlogCur; break; case 2: if(!lstrcmp(szComparer, dlogCur->GetMainExt())) return dlogCur; break; case 3: if(!lstrcmp(szComparer, dlogCur->GetMainUrl())) return dlogCur; break; default: break; } } iSwitch++; } // No match of any of the non-NULL passed in names was found return NULL; } DebugLogElement::DebugLogElement(const DebugLogElement &ref) { SetLogMessage(ref.m_szMessage); } DebugLogElement::DebugLogElement(LPSTR szMessage) : m_szMessage(NULL) { SetLogMessage(szMessage); } DebugLogElement::~DebugLogElement() { if (m_szMessage != NULL) { delete [] m_szMessage; } } HRESULT DebugLogElement::SetLogMessage(LPSTR szMessage) { HRESULT hr = S_OK; if (m_szMessage != NULL) { delete [] m_szMessage; m_szMessage = NULL; } m_szMessage = new char[strlen(szMessage) + 1]; if (m_szMessage != NULL) { strcpy(m_szMessage, szMessage); } else { hr = E_OUTOFMEMORY; } return hr; }