/** * @file win_crash_logger.cpp * @brief Windows crash logger implementation * * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. * $License$ */ // win_crash_logger.cpp : Defines the entry point for the application. // // Must be first include, precompiled headers. #include "stdafx.h" #include "linden_common.h" #include "llcontrol.h" #include "resource.h" #include #include #include #include #include #include #include #include "indra_constants.h" // CRASH_BEHAVIOR_ASK, CRASH_SETTING_NAME #include "llerror.h" #include "lltimer.h" #include "lldir.h" #include "llstring.h" #include "lldxhardware.h" LLControlGroup gCrashSettings; // saved at end of session // Constants #define MAX_LOADSTRING 100 const char* const SETTINGS_FILE_HEADER = "version"; const S32 SETTINGS_FILE_VERSION = 101; // Functions LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); bool handle_button_click(WORD button_id); S32 load_crash_behavior_setting(); bool save_crash_behavior_setting(S32 crash_behavior); void send_crash_report(); void write_debug(const char *str); void write_debug(std::string& str); // Global Variables: HINSTANCE hInst= NULL; // current instance TCHAR szTitle[MAX_LOADSTRING]; /* Flawfinder: ignore */ // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; /* Flawfinder: ignore */ // The title bar text LLString gUserText; // User's description of the problem time_t gStartTime = 0; HWND gHwndReport = NULL; // Send/Don't Send dialog HWND gHwndProgress = NULL; // Progress window HCURSOR gCursorArrow = NULL; HCURSOR gCursorWait = NULL; BOOL gFirstDialog = TRUE; // Are we currently handling the Send/Don't Send dialog? BOOL gCrashInPreviousExec = FALSE; FILE *gDebugFile = NULL; LLString gUserserver; WCHAR gProductName[512]; // // Implementation // // Include product name in the window caption. void ProcessCaption(HWND hWnd) { TCHAR templateText[1024]; /* Flawfinder: ignore */ TCHAR finalText[2048]; /* Flawfinder: ignore */ GetWindowText(hWnd, templateText, sizeof(templateText)); swprintf(finalText, templateText, gProductName); /* Flawfinder: ignore */ SetWindowText(hWnd, finalText); } // Include product name in the diaog item text. void ProcessDlgItemText(HWND hWnd, int nIDDlgItem) { TCHAR templateText[1024]; /* Flawfinder: ignore */ TCHAR finalText[2048]; /* Flawfinder: ignore */ GetDlgItemText(hWnd, nIDDlgItem, templateText, sizeof(templateText)); swprintf(finalText, templateText, gProductName); /* Flawfinder: ignore */ SetDlgItemText(hWnd, nIDDlgItem, finalText); } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { llinfos << "Starting crash reporter" << llendl; // We assume that all the logs we're looking for reside on the current drive gDirUtilp->initAppDirs("SecondLife"); // Default to the product name "Second Life" (this is overridden by the -name argument) swprintf(gProductName, L"Second Life"); /* Flawfinder: ignore */ gCrashSettings.declareS32(CRASH_BEHAVIOR_SETTING, CRASH_BEHAVIOR_ASK, "Controls behavior when viewer crashes " "(0 = ask before sending crash report, 1 = always send crash report, 2 = never send crash report)"); llinfos << "Loading crash behavior setting" << llendl; S32 crash_behavior = load_crash_behavior_setting(); // In Win32, we need to generate argc and argv ourselves... // Note: GetCommandLine() returns a potentially return a LPTSTR // which can resolve to a LPWSTR (unicode string). // (That's why it's different from lpCmdLine which is a LPSTR.) // We don't currently do unicode, so call the non-unicode version // directly. llinfos << "Processing command line" << llendl; LPSTR cmd_line_including_exe_name = GetCommandLineA(); const S32 MAX_ARGS = 100; int argc = 0; char *argv[MAX_ARGS]; /* Flawfinder: ignore */ char *token = NULL; if( cmd_line_including_exe_name[0] == '\"' ) { // Exe name is enclosed in quotes token = strtok( cmd_line_including_exe_name, "\"" ); argv[argc++] = token; token = strtok( NULL, " \t," ); } else { // Exe name is not enclosed in quotes token = strtok( cmd_line_including_exe_name, " \t," ); } while( (token != NULL) && (argc < MAX_ARGS) ) { argv[argc++] = token; /* Get next token: */ if (*(token + strlen(token) + 1) == '\"') /* Flawfinder: ignore */ { token = strtok( NULL, "\""); } else { token = strtok( NULL, " \t," ); } } S32 i; for (i=0; igetExpandedFilename(LL_PATH_LOGS,"debug_info.log"); db_filep = new LLFileEncoder("DB", db_file_name.c_str()); // Get the filename of the SecondLife.log file // *NOTE: This buffer size is hard coded into scanf() below. char tmp_sl_name[256]; /* Flawfinder: ignore */ tmp_sl_name[0] = '\0'; update_messages(); show_progress("Looking for files..."); update_messages(); // Look for it in the debug_info.log file if (db_filep->isValid()) { sscanf( (const char*)db_filep->mBuf, "SL Log: %255[^\r\n]", tmp_sl_name); } else { delete db_filep; db_filep = NULL; } if (gCrashInPreviousExec) { // If we froze, the crash log this time around isn't useful. Use the // old one. sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.old"); } else if (tmp_sl_name[0]) { // If debug_info.log gives us a valid log filename, use that. sl_file_name = tmp_sl_name; llinfos << "Using log file from debug log " << sl_file_name << llendl; } else { // Figure out the filename of the default second life log sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log"); } // Now we get the SecondLife.log file if it's there sl_filep = new LLFileEncoder("SL", sl_file_name.c_str()); if (!sl_filep->isValid()) { delete sl_filep; sl_filep = NULL; } update_messages(); show_progress("Looking for stats file..."); update_messages(); st_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log"); st_filep = new LLFileEncoder("ST", st_file_name.c_str()); if (!st_filep->isValid()) { delete st_filep; st_filep = NULL; } si_file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.ini"); si_filep = new LLFileEncoder("SI", si_file_name.c_str()); if (!si_filep->isValid()) { delete si_filep; si_filep = NULL; } // Now we get the minidump md_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.dmp"); md_filep = new LLFileEncoder("MD", md_file_name.c_str()); if (!md_filep->isValid()) { delete md_filep; md_filep = NULL; } // Now we get the message log ml_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"message.log"); ml_filep = new LLFileEncoder("ML", ml_file_name.c_str()); if (!ml_filep->isValid()) { delete ml_filep; ml_filep = NULL; } LLString post_data; LLString tmp_url_buf; // Append the userserver tmp_url_buf = encode_string("USER", gUserserver); post_data += tmp_url_buf; llinfos << "PostData:" << post_data << llendl; if (gCrashInPreviousExec) { post_data.append(1, '&'); tmp_url_buf = encode_string("EF", "Y"); post_data += tmp_url_buf; } update_messages(); show_progress("Encoding data"); update_messages(); if (db_filep) { post_data.append(1, '&'); tmp_url_buf = db_filep->encodeURL(); post_data += tmp_url_buf; llinfos << "Sending DB log file" << llendl; } else { llinfos << "Not sending DB log file" << llendl; } show_progress("Encoding data."); update_messages(); if (sl_filep) { post_data.append(1, '&'); tmp_url_buf = sl_filep->encodeURL(SL_MAX_SIZE); post_data += tmp_url_buf; llinfos << "Sending SL log file" << llendl; } else { llinfos << "Not sending SL log file" << llendl; } show_progress("Encoding data.."); update_messages(); if (st_filep) { post_data.append(1, '&'); tmp_url_buf = st_filep->encodeURL(SL_MAX_SIZE); post_data += tmp_url_buf; llinfos << "Sending stats log file" << llendl; } else { llinfos << "Not sending stats log file" << llendl; } show_progress("Encoding data..."); update_messages(); if (md_filep) { post_data.append(1, '&'); tmp_url_buf = md_filep->encodeURL(); post_data += tmp_url_buf; llinfos << "Sending minidump log file" << llendl; } else { llinfos << "Not sending minidump log file" << llendl; } show_progress("Encoding data...."); update_messages(); if (si_filep) { post_data.append(1, '&'); tmp_url_buf = si_filep->encodeURL(); post_data += tmp_url_buf; llinfos << "Sending settings log file" << llendl; } else { llinfos << "Not sending settings.ini file" << llendl; } show_progress("Encoding data...."); update_messages(); if (ml_filep) { post_data.append(1, '&'); tmp_url_buf = ml_filep->encodeURL(SL_MAX_SIZE); post_data += tmp_url_buf; llinfos << "Sending message log file" << llendl; } else { llinfos << "Not sending message.log file" << llendl; } show_progress("Encoding data...."); update_messages(); if (gUserText.size()) { post_data.append(1, '&'); tmp_url_buf = encode_string("UN", gUserText); post_data += tmp_url_buf; } delete db_filep; db_filep = NULL; delete sl_filep; sl_filep = NULL; delete md_filep; md_filep = NULL; // Post data to web server const S32 BUFSIZE = 65536; HINTERNET hinet, hsession, hrequest; char data[BUFSIZE]; /* Flawfinder: ignore */ unsigned long bytes_read; llinfos << "Connecting to crash report server" << llendl; update_messages(); show_progress("Connecting to server..."); update_messages(); // Init wininet subsystem hinet = InternetOpen(L"LindenCrashReporter", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (hinet == NULL) { llinfos << "Couldn't open connection" << llendl; sleep_and_pump_messages( 5 ); // return FALSE; } hsession = InternetConnect(hinet, L"secondlife.com", INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL); if (!hsession) { llinfos << "Couldn't talk to crash report server" << llendl; } hrequest = HttpOpenRequest(hsession, L"POST", L"/cgi-bin/viewer_crash_reporter2", NULL, L"", NULL, 0, 0); if (!hrequest) { llinfos << "Couldn't open crash report URL!" << llendl; } llinfos << "Transmitting data" << llendl; llinfos << "Bytes: " << (post_data.size()) << llendl; update_messages(); show_progress("Transmitting data..."); update_messages(); BOOL ok = HttpSendRequest(hrequest, NULL, 0, (void *)(post_data.c_str()), post_data.size()); if (!ok) { llinfos << "Error posting data!" << llendl; sleep_and_pump_messages( 5 ); } llinfos << "Response from crash report server:" << llendl; do { if (InternetReadFile(hrequest, data, BUFSIZE, &bytes_read)) { if (bytes_read == 0) { // If InternetFileRead returns TRUE AND bytes_read == 0 // we've successfully downloaded the entire file break; } else { data[bytes_read] = 0; llinfos << data << llendl; } } else { llinfos << "Couldn't read file!" << llendl; sleep_and_pump_messages( 5 ); // return FALSE; } } while(TRUE); InternetCloseHandle(hrequest); InternetCloseHandle(hsession); InternetCloseHandle(hinet); update_messages(); show_progress("Done."); sleep_and_pump_messages( 3 ); // return TRUE; } LLFileEncoder::LLFileEncoder(const char *form_name, const char *filename) { mFormname = form_name; mFilename = filename; mIsValid = FALSE; mBuf = NULL; int res; llstat stat_data; res = LLFile::stat(mFilename.c_str(), &stat_data); if (res) { llwarns << "File " << mFilename << " is missing!" << llendl; return; } FILE *fp = NULL; S32 buf_size = 0; S32 count = 0; while (count < 5) { buf_size = stat_data.st_size; fp = LLFile::fopen(mFilename.c_str(), "rb"); /* Flawfinder: ignore */ if (!fp) { llwarns << "Can't open file " << mFilename << ", wait for a second" << llendl; // Couldn't open the file, wait a bit and try again count++; ms_sleep(1000); } else { break; } } if (!fp) { return; } U8 *buf = new U8[buf_size + 1]; fread(buf, 1, buf_size, fp); fclose(fp); mBuf = buf; mBufLength = buf_size; mIsValid = TRUE; } LLFileEncoder::~LLFileEncoder() { if (mBuf) { delete mBuf; mBuf = NULL; } } LLString LLFileEncoder::encodeURL(const S32 max_length) { LLString result = mFormname; result.append(1, '='); S32 i = 0; if (max_length) { if (mBufLength > max_length) { i = mBufLength - max_length; } } S32 url_buf_size = 3*mBufLength + 1; char *url_buf = new char[url_buf_size]; S32 cur_pos = 0; for (; i < mBufLength; i++) { S32 byte_val = mBuf[i]; sprintf(url_buf + cur_pos, "%%%02x", byte_val); cur_pos += 3; } url_buf[i*3] = 0; result.append(url_buf); delete[] url_buf; return result; } LLString encode_string(const char *formname, const LLString &str) { LLString result = formname; result.append(1, '='); // Not using LLString because of bad performance issues S32 buf_size = str.size(); S32 url_buf_size = 3*str.size() + 1; char *url_buf = new char[url_buf_size]; S32 cur_pos = 0; S32 i; for (i = 0; i < buf_size; i++) { sprintf(url_buf + cur_pos, "%%%02x", str[i]); cur_pos += 3; } url_buf[i*3] = 0; result.append(url_buf); delete[] url_buf; return result; } void write_debug(const char *str) { if (!gDebugFile) { std::string debug_filename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log"); llinfos << "Opening debug file " << debug_filename << llendl; gDebugFile = LLFile::fopen(debug_filename.c_str(), "a+"); /* Flawfinder: ignore */ if (!gDebugFile) { fprintf(stderr, "Couldn't open %s: debug log to stderr instead.\n", debug_filename.c_str()); gDebugFile = stderr; } } fprintf(gDebugFile, str); /* Flawfinder: ignore */ fflush(gDebugFile); } void write_debug(std::string& str) { write_debug(str.c_str()); } S32 load_crash_behavior_setting() { std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE); gCrashSettings.loadFromFile(filename); S32 value = gCrashSettings.getS32(CRASH_BEHAVIOR_SETTING); if (value < CRASH_BEHAVIOR_ASK || CRASH_BEHAVIOR_NEVER_SEND < value) return CRASH_BEHAVIOR_ASK; return value; } bool save_crash_behavior_setting(S32 crash_behavior) { if (crash_behavior < CRASH_BEHAVIOR_ASK) return false; if (crash_behavior > CRASH_BEHAVIOR_NEVER_SEND) return false; gCrashSettings.setS32(CRASH_BEHAVIOR_SETTING, crash_behavior); std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE); gCrashSettings.saveToFile(filename, FALSE); return true; }