libgit2 requires zlib
[TortoiseGit.git] / src / crashrpt / CrashHandler.cpp
blob35699432a80dceb2bdc709731bd2965cd580296f
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // Module: CrashHandler.cpp
4 //
5 // Desc: See CrashHandler.h
6 //
7 // Copyright (c) 2003 Michael Carruth
8 //
9 ///////////////////////////////////////////////////////////////////////////////
11 #include "stdafx.h"
12 #include "CrashHandler.h"
13 #include "zlibcpp.h"
14 #include "excprpt.h"
15 #include "maindlg.h"
16 #include "mailmsg.h"
17 #include "WriteRegistry.h"
18 #include "resource.h"
20 #include <windows.h>
21 #include <shlwapi.h>
22 #include <commctrl.h>
23 #pragma comment(lib, "shlwapi")
24 #pragma comment(lib, "comctl32")
26 #include <algorithm>
28 BOOL g_bNoCrashHandler;// don't use the crash handler but let the system handle it
30 // maps crash objects to processes
31 map<DWORD, CCrashHandler*> _crashStateMap;
33 // unhandled exception callback set with SetUnhandledExceptionFilter()
34 LONG WINAPI CustomUnhandledExceptionFilter(PEXCEPTION_POINTERS pExInfo)
36 OutputDebugString("Exception\n");
37 if (EXCEPTION_BREAKPOINT == pExInfo->ExceptionRecord->ExceptionCode)
39 // Breakpoint. Don't treat this as a normal crash.
40 return EXCEPTION_CONTINUE_SEARCH;
43 if (g_bNoCrashHandler)
45 return EXCEPTION_CONTINUE_SEARCH;
48 BOOL result = false;
49 if (_crashStateMap.find(GetCurrentProcessId()) != _crashStateMap.end())
50 result = _crashStateMap[GetCurrentProcessId()]->GenerateErrorReport(pExInfo, NULL);
52 // If we're in a debugger, return EXCEPTION_CONTINUE_SEARCH to cause the debugger to stop;
53 // or if GenerateErrorReport returned FALSE (i.e. drop into debugger).
54 return (!result || IsDebuggerPresent()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER;
57 CCrashHandler * CCrashHandler::GetInstance()
59 CCrashHandler *instance = NULL;
60 if (_crashStateMap.find(GetCurrentProcessId()) != _crashStateMap.end())
61 instance = _crashStateMap[GetCurrentProcessId()];
62 if (instance == NULL) {
63 // will register
64 instance = new CCrashHandler();
66 return instance;
69 CCrashHandler::CCrashHandler():
70 m_oldFilter(NULL),
71 m_lpfnCallback(NULL),
72 m_pid(GetCurrentProcessId()),
73 m_ipc_event(NULL),
74 m_rpt(NULL),
75 m_installed(false),
76 m_hModule(NULL),
77 m_bUseUI(TRUE),
78 m_wantDebug(false)
80 // wtl initialization stuff...
81 HRESULT hRes = ::CoInitialize(NULL);
82 if (hRes != S_OK)
83 m_pid = 0;
84 else
85 _crashStateMap[m_pid] = this;
88 void CCrashHandler::Install(LPGETLOGFILE lpfn, LPCTSTR lpcszTo, LPCTSTR lpcszSubject, BOOL bUseUI)
90 if (m_pid == 0)
91 return;
92 #ifdef _DEBUG
93 OutputDebugString("::Install\n");
94 #endif
95 if (m_installed) {
96 Uninstall();
98 // save user supplied callback
99 m_lpfnCallback = lpfn;
100 // save optional email info
101 m_sTo = lpcszTo;
102 m_sSubject = lpcszSubject;
103 m_bUseUI = bUseUI;
105 // This is needed for CRT to not show dialog for invalid param
106 // failures and instead let the code handle it.
107 _CrtSetReportMode(_CRT_ASSERT, 0);
108 // add this filter in the exception callback chain
109 m_oldFilter = SetUnhandledExceptionFilter(CustomUnhandledExceptionFilter);
110 /*m_oldErrorMode=*/ SetErrorMode( SEM_FAILCRITICALERRORS );
112 m_installed = true;
115 void CCrashHandler::Uninstall()
117 #ifdef _DEBUG
118 OutputDebugString("Uninstall\n");
119 #endif
120 // reset exception callback (to previous filter, which can be NULL)
121 SetUnhandledExceptionFilter(m_oldFilter);
122 m_installed = false;
125 void CCrashHandler::EnableUI()
127 m_bUseUI = TRUE;
130 void CCrashHandler::DisableUI()
132 m_bUseUI = FALSE;
135 void CCrashHandler::DisableHandler()
137 g_bNoCrashHandler = TRUE;
140 void CCrashHandler::EnableHandler()
142 g_bNoCrashHandler = FALSE;
145 CCrashHandler::~CCrashHandler()
148 Uninstall();
150 _crashStateMap.erase(m_pid);
152 ::CoUninitialize();
156 void CCrashHandler::AddFile(LPCTSTR lpFile, LPCTSTR lpDesc)
158 // make sure we don't already have the file
159 RemoveFile(lpFile);
160 // make sure the file exists
161 HANDLE hFile = ::CreateFile(
162 lpFile,
163 GENERIC_READ,
164 FILE_SHARE_READ | FILE_SHARE_WRITE,
165 NULL,
166 OPEN_EXISTING,
167 FILE_ATTRIBUTE_NORMAL,
169 if (hFile != INVALID_HANDLE_VALUE)
171 // add file to report
172 m_files.push_back(TStrStrPair(lpFile, lpDesc));
173 ::CloseHandle(hFile);
177 void CCrashHandler::RemoveFile(LPCTSTR lpFile)
179 TStrStrVector::iterator iter;
180 for (iter = m_files.begin(); iter != m_files.end(); ++iter) {
181 if ((*iter).first == lpFile) {
182 iter = m_files.erase(iter);
183 break;
188 void CCrashHandler::AddRegistryHive(LPCTSTR lpRegistryHive, LPCTSTR lpDesc)
190 // make sure we don't already have the RegistryHive
191 RemoveRegistryHive(lpRegistryHive);
192 // Unfortunately, we have no easy way of verifying the existence of
193 // the registry hive.
194 // add RegistryHive to report
195 m_registryHives.push_back(TStrStrPair(lpRegistryHive, lpDesc));
198 void CCrashHandler::RemoveRegistryHive(LPCTSTR lpRegistryHive)
200 TStrStrVector::iterator iter;
201 for (iter = m_registryHives.begin(); iter != m_registryHives.end(); ++iter) {
202 if ((*iter).first == lpRegistryHive) {
203 iter = m_registryHives.erase(iter);
208 void CCrashHandler::AddEventLog(LPCTSTR lpEventLog, LPCTSTR lpDesc)
210 // make sure we don't already have the EventLog
211 RemoveEventLog(lpEventLog);
212 // Unfortunately, we have no easy way of verifying the existence of
213 // the event log..
214 // add EventLog to report
215 m_eventLogs.push_back(TStrStrPair(lpEventLog, lpDesc));
218 void CCrashHandler::RemoveEventLog(LPCTSTR lpEventLog)
220 TStrStrVector::iterator iter;
221 for (iter = m_eventLogs.begin(); iter != m_eventLogs.end(); ++iter) {
222 if ((*iter).first == lpEventLog) {
223 iter = m_eventLogs.erase(iter);
228 DWORD WINAPI CCrashHandler::DialogThreadExecute(LPVOID pParam)
230 // New thread. This will display the dialog and handle the result.
231 CCrashHandler * self = reinterpret_cast<CCrashHandler *>(pParam);
232 CMainDlg mainDlg;
233 string sTempFileName = CUtility::getTempFileName();
234 CZLib zlib;
236 // delete existing copy, if any
237 DeleteFile(sTempFileName.c_str());
239 // zip the report
240 if (!zlib.Open(sTempFileName))
241 return TRUE;
243 // add report files to zip
244 TStrStrVector::iterator cur = self->m_files.begin();
245 for (cur = self->m_files.begin(); cur != self->m_files.end(); cur++)
246 if (PathFileExists((*cur).first.c_str()))
247 zlib.AddFile((*cur).first);
249 zlib.Close();
251 mainDlg.m_pUDFiles = &self->m_files;
252 mainDlg.m_sendButton = !self->m_sTo.empty();
254 INITCOMMONCONTROLSEX used = {
255 sizeof(INITCOMMONCONTROLSEX),
256 ICC_LISTVIEW_CLASSES | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_USEREX_CLASSES
258 InitCommonControlsEx(&used);
260 INT_PTR status = mainDlg.DoModal(GetModuleHandle("CrashRpt.dll"), IDD_MAINDLG, GetDesktopWindow());
261 if (IDOK == status || IDC_SAVE == status)
263 if (IDC_SAVE == status || self->m_sTo.empty() ||
264 !self->MailReport(*self->m_rpt, sTempFileName.c_str(), mainDlg.m_sEmail.c_str(), mainDlg.m_sDescription.c_str()))
266 // write user data to file if to be supplied
267 if (!self->m_userDataFile.empty()) {
268 HANDLE hFile = ::CreateFile(
269 self->m_userDataFile.c_str(),
270 GENERIC_READ | GENERIC_WRITE,
271 FILE_SHARE_READ | FILE_SHARE_WRITE,
272 NULL,
273 CREATE_ALWAYS,
274 FILE_ATTRIBUTE_NORMAL,
276 if (hFile != INVALID_HANDLE_VALUE)
278 static const char e_mail[] = "E-mail:";
279 static const char newline[] = "\r\n";
280 static const char description[] = "\r\n\r\nDescription:";
281 DWORD writtenBytes;
282 ::WriteFile(hFile, e_mail, sizeof(e_mail) - 1, &writtenBytes, NULL);
283 ::WriteFile(hFile, mainDlg.m_sEmail.c_str(), mainDlg.m_sEmail.size(), &writtenBytes, NULL);
284 ::WriteFile(hFile, description, sizeof(description) - 1, &writtenBytes, NULL);
285 ::WriteFile(hFile, mainDlg.m_sDescription.c_str(), mainDlg.m_sDescription.size(), &writtenBytes, NULL);
286 ::WriteFile(hFile, newline, sizeof(newline) - 1, &writtenBytes, NULL);
287 ::CloseHandle(hFile);
288 // redo zip file to add user data
289 // delete existing copy, if any
290 DeleteFile(sTempFileName.c_str());
292 // zip the report
293 if (!zlib.Open(sTempFileName))
294 return TRUE;
296 // add report files to zip
297 TStrStrVector::iterator cur = self->m_files.begin();
298 for (cur = self->m_files.begin(); cur != self->m_files.end(); cur++)
299 if (PathFileExists((*cur).first.c_str()))
300 zlib.AddFile((*cur).first);
302 zlib.Close();
305 self->SaveReport(*self->m_rpt, sTempFileName.c_str());
309 DeleteFile(sTempFileName.c_str());
311 self->m_wantDebug = IDC_DEBUG == status;
312 // signal main thread to resume
313 ::SetEvent(self->m_ipc_event);
314 // set flag for debugger break
316 // exit thread
317 ::ExitThread(0);
318 // keep compiler happy.
319 return TRUE;
322 BOOL CCrashHandler::GenerateErrorReport(PEXCEPTION_POINTERS pExInfo, BSTR message)
324 CExceptionReport rpt(pExInfo, message);
325 unsigned int i;
326 // save state of file list prior to generating report
327 TStrStrVector save_m_files = m_files;
328 char temp[_MAX_PATH];
330 GetTempPath(sizeof temp, temp);
333 // let client add application specific files to report
334 if (m_lpfnCallback && !m_lpfnCallback(this))
335 return TRUE;
337 m_rpt = &rpt;
339 // if no e-mail address, add file to contain user data
340 m_userDataFile = "";
341 if (m_sTo.empty()) {
342 m_userDataFile = temp + string("\\") + CUtility::getAppName() + "_UserInfo.txt";
343 HANDLE hFile = ::CreateFile(
344 m_userDataFile.c_str(),
345 GENERIC_READ | GENERIC_WRITE,
346 FILE_SHARE_READ | FILE_SHARE_WRITE,
347 NULL,
348 CREATE_ALWAYS,
349 FILE_ATTRIBUTE_NORMAL,
351 if (hFile != INVALID_HANDLE_VALUE)
353 static const char description[] = "Your e-mail and description will go here.";
354 DWORD writtenBytes;
355 ::WriteFile(hFile, description, sizeof(description)-1, &writtenBytes, NULL);
356 ::CloseHandle(hFile);
357 m_files.push_back(TStrStrPair(m_userDataFile, LoadResourceString(IDS_USER_DATA)));
358 } else {
359 return m_wantDebug;
365 // add crash files to report
366 string crashFile = rpt.getCrashFile();
367 string crashLog = rpt.getCrashLog();
368 m_files.push_back(TStrStrPair(crashFile, LoadResourceString(IDS_CRASH_DUMP)));
369 m_files.push_back(TStrStrPair(crashLog, LoadResourceString(IDS_CRASH_LOG)));
371 // Export registry hives and add to report
372 std::vector<string> extraFiles;
373 string file;
374 string number;
375 TStrStrVector::iterator iter;
376 int n = 0;
378 for (iter = m_registryHives.begin(); iter != m_registryHives.end(); iter++) {
379 ++n;
380 TCHAR buf[MAX_PATH] = {0};
381 _tprintf_s(buf, "%d", n);
382 number = buf;
383 file = temp + string("\\") + CUtility::getAppName() + "_registry" + number + ".reg";
384 ::DeleteFile(file.c_str());
386 // we want to export in a readable format. Unfortunately, RegSaveKey saves in a binary
387 // form, so let's use our own function.
388 if (WriteRegistryTreeToFile((*iter).first.c_str(), file.c_str())) {
389 extraFiles.push_back(file);
390 m_files.push_back(TStrStrPair(file, (*iter).second));
391 } else {
392 OutputDebugString("Could not write registry hive\n");
396 // Add the specified event log(s). Note that this will not work on Win9x/WinME.
398 for (iter = m_eventLogs.begin(); iter != m_eventLogs.end(); iter++) {
399 HANDLE h;
400 h = OpenEventLog( NULL, // use local computer
401 (*iter).first.c_str()); // source name
402 if (h != NULL) {
404 file = temp + string("\\") + CUtility::getAppName() + "_" + (*iter).first + ".evt";
406 DeleteFile(file.c_str());
408 if (BackupEventLog(h, file.c_str())) {
409 m_files.push_back(TStrStrPair(file, (*iter).second));
410 extraFiles.push_back(file);
411 } else {
412 OutputDebugString("could not backup log\n");
414 CloseEventLog(h);
415 } else {
416 OutputDebugString("could not open log\n");
421 // add symbol files to report
422 for (i = 0; i < (UINT)rpt.getNumSymbolFiles(); i++)
423 m_files.push_back(TStrStrPair(rpt.getSymbolFile(i).c_str(),
424 string("Symbol File")));
426 //remove the crash handler, just in case the dialog crashes...
427 Uninstall();
428 if (m_bUseUI)
430 // Start a new thread to display the dialog, and then wait
431 // until it completes
432 m_ipc_event = ::CreateEvent(NULL, FALSE, FALSE, "ACrashHandlerEvent");
433 if (m_ipc_event == NULL)
434 return m_wantDebug;
435 DWORD threadId;
436 if (::CreateThread(NULL, 0, DialogThreadExecute,
437 reinterpret_cast<LPVOID>(this), 0, &threadId) == NULL)
438 return m_wantDebug;
439 ::WaitForSingleObject(m_ipc_event, INFINITE);
440 CloseHandle(m_ipc_event);
442 else
444 string sTempFileName = CUtility::getTempFileName();
445 CZLib zlib;
447 sTempFileName += _T(".zip");
448 // delete existing copy, if any
449 DeleteFile(sTempFileName.c_str());
451 // zip the report
452 if (!zlib.Open(sTempFileName))
453 return TRUE;
455 // add report files to zip
456 TStrStrVector::iterator cur = m_files.begin();
457 for (cur = m_files.begin(); cur != m_files.end(); cur++)
458 if (PathFileExists((*cur).first.c_str()))
459 zlib.AddFile((*cur).first);
460 zlib.Close();
461 fprintf(stderr, "a zipped crash report has been saved to\n");
462 _ftprintf(stderr, sTempFileName.c_str());
463 fprintf(stderr, "\n");
464 if (!m_sTo.empty())
466 fprintf(stderr, "please send the report to ");
467 _ftprintf(stderr, m_sTo.c_str());
468 fprintf(stderr, "\n");
471 // clean up - delete files we created
472 ::DeleteFile(crashFile.c_str());
473 ::DeleteFile(crashLog.c_str());
474 if (!m_userDataFile.empty()) {
475 ::DeleteFile(m_userDataFile.c_str());
478 std::vector<string>::iterator file_iter;
479 for (file_iter = extraFiles.begin(); file_iter != extraFiles.end(); file_iter++) {
480 ::DeleteFile(file_iter->c_str());
483 // restore state of file list
484 m_files = save_m_files;
486 m_rpt = NULL;
488 return !m_wantDebug;
491 BOOL CCrashHandler::SaveReport(CExceptionReport&, LPCTSTR lpcszFile)
493 // let user more zipped report
494 return (CopyFile(lpcszFile, CUtility::getSaveFileName().c_str(), TRUE));
497 BOOL CCrashHandler::MailReport(CExceptionReport&, LPCTSTR lpcszFile,
498 LPCTSTR lpcszEmail, LPCTSTR lpcszDesc)
500 CMailMsg msg;
502 .SetTo(m_sTo)
503 .SetFrom(lpcszEmail)
504 .SetSubject(m_sSubject.empty()?_T("Incident Report"):m_sSubject)
505 .SetMessage(lpcszDesc)
506 .AddAttachment(lpcszFile, CUtility::getAppName() + _T(".zip"));
508 return (msg.Send());
511 string CCrashHandler::LoadResourceString(UINT id)
513 static int address;
514 char buffer[512];
515 if (m_hModule == NULL) {
516 m_hModule = GetModuleHandle("CrashRpt.dll");
518 buffer[0] = '\0';
519 if (m_hModule) {
520 LoadString(m_hModule, id, buffer, sizeof buffer);
522 return buffer;