diff options
Diffstat (limited to 'indra/mac_crash_logger')
-rw-r--r-- | indra/mac_crash_logger/mac_crash_logger.cpp | 669 |
1 files changed, 669 insertions, 0 deletions
diff --git a/indra/mac_crash_logger/mac_crash_logger.cpp b/indra/mac_crash_logger/mac_crash_logger.cpp new file mode 100644 index 0000000000..5e6d9ba2f0 --- /dev/null +++ b/indra/mac_crash_logger/mac_crash_logger.cpp @@ -0,0 +1,669 @@ +/** + * @file mac_crash_logger.cpp + * @brief Mac OSX crash logger implementation + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include <stdio.h> +#include <stdlib.h> +//#include <direct.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <curl/curl.h> + +#include "llerror.h" +#include "lltimer.h" +#include "lldir.h" + +#include "llstring.h" + +class LLFileEncoder +{ +public: + LLFileEncoder(const char *formname, const char *filename, bool isCrashLog = false); + + BOOL isValid() const { return mIsValid; } + LLString encodeURL(const S32 max_length = 0); +public: + BOOL mIsValid; + LLString mFilename; + LLString mFormname; + LLString mBuf; +}; + +LLString encode_string(const char *formname, const LLString &str); + +#include <Carbon/Carbon.h> + +LLString gServerResponse; +BOOL gSendReport = FALSE; +LLString gUserserver; +LLString gUserText; +WindowRef gWindow = NULL; +EventHandlerRef gEventHandler = NULL; +BOOL gCrashInPreviousExec = FALSE; +time_t gLaunchTime; + +size_t curl_download_callback(void *data, size_t size, size_t nmemb, + void *user_data) +{ + S32 bytes = size * nmemb; + char *cdata = (char *) data; + for (int i =0; i < bytes; i += 1) + { + gServerResponse += (cdata[i]); + } + return bytes; +} + +OSStatus dialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata) +{ + OSStatus result = eventNotHandledErr; + OSStatus err; + UInt32 evtClass = GetEventClass(event); + UInt32 evtKind = GetEventKind(event); + + if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess)) + { + HICommand cmd; + err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd); + + if(err == noErr) + { + switch(cmd.commandID) + { + case kHICommandOK: + { + char buffer[65535]; + Size size = sizeof(buffer) - 1; + ControlRef textField = NULL; + ControlID id; + + id.signature = 'text'; + id.id = 0; + + err = GetControlByID(gWindow, &id, &textField); + if(err == noErr) + { + // Get the user response text + err = GetControlData(textField, kControlNoPart, kControlEditTextTextTag, size, (Ptr)buffer, &size); + } + if(err == noErr) + { + // Make sure the string is terminated. + buffer[size] = 0; + gUserText = buffer; + llinfos << buffer << llendl; + } + + // Send the report. + gSendReport = TRUE; + + QuitAppModalLoopForWindow(gWindow); + result = noErr; + } + break; + + case kHICommandCancel: + QuitAppModalLoopForWindow(gWindow); + result = noErr; + break; + } + } + } + + return(result); +} + +int main(int argc, char **argv) +{ + const S32 DW_MAX_SIZE = 100000; // Maximum size to transmit of the Dr. Watson log file + const S32 SL_MAX_SIZE = 100000; // Maximum size of the Second Life log file. + int i; + + time(&gLaunchTime); + + llinfos << "Starting Second Life Viewer Crash Reporter" << llendl; + + for(i=1; i<argc; i++) + { + if(!strcmp(argv[i], "-previous")) + { + gCrashInPreviousExec = TRUE; + } + if(!strcmp(argv[i], "-user")) + { + if ((i + 1) < argc) + { + i++; + gUserserver = argv[i]; + llinfos << "Got userserver " << gUserserver << llendl; + } + } + } + + if( gCrashInPreviousExec ) + { + llinfos << "Previous execution did not remove SecondLife.exec_marker" << llendl; + } + + if(!gCrashInPreviousExec) + { + // Delay five seconds to let CrashReporter do its thing. + sleep(5); + } + +#if 1 + // Real UI... + OSStatus err; + IBNibRef nib = NULL; + + err = CreateNibReference(CFSTR("CrashReporter"), &nib); + + if(err == noErr) + { + if(gCrashInPreviousExec) + { + err = CreateWindowFromNib(nib, CFSTR("CrashReporterDelayed"), &gWindow); + } + else + { + err = CreateWindowFromNib(nib, CFSTR("CrashReporter"), &gWindow); + } + } + + if(err == noErr) + { + // Set focus to the edit text area + ControlRef textField = NULL; + ControlID id; + + id.signature = 'text'; + id.id = 0; + + // Don't set err if any of this fails, since it's non-critical. + if(GetControlByID(gWindow, &id, &textField) == noErr) + { + SetKeyboardFocus(gWindow, textField, kControlFocusNextPart); + } + } + + if(err == noErr) + { + ShowWindow(gWindow); + } + + if(err == noErr) + { + // Set up an event handler for the window. + EventTypeSpec handlerEvents[] = + { + { kEventClassCommand, kEventCommandProcess } + }; + + InstallWindowEventHandler( + gWindow, + NewEventHandlerUPP(dialogHandler), + GetEventTypeCount (handlerEvents), + handlerEvents, + 0, + &gEventHandler); + } + + if(err == noErr) + { + RunAppModalLoopForWindow(gWindow); + } + + if(gWindow != NULL) + { + DisposeWindow(gWindow); + } + + if(nib != NULL) + { + DisposeNibReference(nib); + } +#else + // Cheap version -- just use the standard system alert. + SInt16 itemHit = 0; + AlertStdCFStringAlertParamRec params; + OSStatus err = noErr; + DialogRef alert = NULL; + + params.version = kStdCFStringAlertVersionOne; + params.movable = false; + params.helpButton = false; + params.defaultText = CFSTR("Send Report"); + params.cancelText = CFSTR("Don't Send Report"); + params.otherText = 0; + params.defaultButton = kAlertStdAlertOKButton; + params.cancelButton = kAlertStdAlertCancelButton; + params.position = kWindowDefaultPosition; + params.flags = 0; + + err = CreateStandardAlert( + kAlertCautionAlert, + CFSTR("Second Life appears to have crashed."), + CFSTR( + "To help us debug the problem, you can send a crash report to Linden Lab. " + "The report contains information about your microprocessor type, graphics card, " + "memory, things that happened during the last run of the program, and the Crash Log " + "of where the crash occurred.\r" + "\r" + "You may also report crashes in the forums, or by sending e-mail to peter@lindenlab.com"), + ¶ms, + &alert); + + if(err == noErr) + { + err = RunStandardAlert( + alert, + NULL, + &itemHit); + } + + if(itemHit == kAlertStdAlertOKButton) + gSendReport = TRUE; +#endif + + if(!gSendReport) + { + // Only send the report if the user agreed to it. + llinfos << "User cancelled, not sending report" << llendl; + + return(0); + } + + // We assume that all the logs we're looking for reside on the current drive + gDirUtilp->initAppDirs("SecondLife"); + + int res; + + // Lots of silly variable, replicated for each log file. + LLString db_file_name; + LLString sl_file_name; + LLString dw_file_name; // DW file name is a hack for now... + LLString st_file_name; // stats.log file + LLString si_file_name; // settings.ini file + + LLFileEncoder *db_filep = NULL; + LLFileEncoder *sl_filep = NULL; + LLFileEncoder *st_filep = NULL; + LLFileEncoder *dw_filep = NULL; + LLFileEncoder *si_filep = NULL; + + /////////////////////////////////// + // + // We do the parsing for the debug_info file first, as that will + // give us the location of the SecondLife.log file. + // + + // Figure out the filename of the debug log + db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log").c_str(); + db_filep = new LLFileEncoder("DB", db_file_name.c_str()); + + // Get the filename of the SecondLife.log file + char tmp_sl_name[MAX_PATH]; + tmp_sl_name[0] = '\0'; + char tmp_space[256]; + tmp_space[0] = '\0'; + + // Look for it in the debug_info.log file + if (db_filep->isValid()) + { + // This was originally scanning for "SL Log: %[^\r\n]", which happily skipped to the next line + // on debug logs (which don't have anything after "SL Log:" and tried to open a nonsensical filename. + sscanf(db_filep->mBuf.c_str(), "SL Log:%[ ]%[^\r\n]", tmp_space, tmp_sl_name); + } + else + { + delete db_filep; + db_filep = NULL; + } + + // If we actually have a legitimate file name, use it. + if (tmp_sl_name[0]) + { + sl_file_name = tmp_sl_name; + llinfos << "Using log file from debug log " << sl_file_name << llendl; + } + else + { + // Figure out the filename of the second life log + sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log").c_str(); + } + + // Now we get the SecondLife.log file if it's there, and recent enough... + sl_filep = new LLFileEncoder("SL", sl_file_name.c_str()); + if (!sl_filep->isValid()) + { + delete sl_filep; + sl_filep = NULL; + } + + st_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log").c_str(); + 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").c_str(); + si_filep = new LLFileEncoder("SI", si_file_name.c_str()); + if (!si_filep->isValid()) + { + delete si_filep; + si_filep = NULL; + } + + // MBW -- This needs to go find "~/Library/Logs/CrashReporter/Second Life.crash.log" on 10.3 + // or "~/Library/Logs/Second Life.crash.log" on 10.2. + { + char path[MAX_PATH]; + FSRef folder; + + if(FSFindFolder(kUserDomain, kLogsFolderType, false, &folder) == noErr) + { + // folder is an FSRef to ~/Library/Logs/ + if(FSRefMakePath(&folder, (UInt8*)&path, sizeof(path)) == noErr) + { + struct stat dw_stat; +// printf("path is %s\n", path); + + // Try the 10.3 path first... + dw_file_name = LLString(path) + LLString("/CrashReporter/Second Life.crash.log"); + res = stat(dw_file_name.c_str(), &dw_stat); + + if (res) + { + // Try the 10.2 one next... + dw_file_name = LLString(path) + LLString("/Second Life.crash.log"); + res = stat(dw_file_name.c_str(), &dw_stat); + } + + if (!res) + { + dw_filep = new LLFileEncoder("DW", dw_file_name.c_str(), true); + if (!dw_filep->isValid()) + { + delete dw_filep; + dw_filep = NULL; + } + } + else + { + llwarns << "Couldn't find any CrashReporter files..." << llendl; + } + } + } + } + + 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("&"); + tmp_url_buf = encode_string("EF", "Y"); + post_data += tmp_url_buf; + } + + if (db_filep) + { + post_data.append("&"); + 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; + } + + if (sl_filep) + { + post_data.append("&"); + 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; + } + + if (st_filep) + { + post_data.append("&"); + 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; + } + + if (dw_filep) + { + post_data.append("&"); + tmp_url_buf = dw_filep->encodeURL(DW_MAX_SIZE); + post_data += tmp_url_buf; + } + else + { + llinfos << "Not sending crash log file" << llendl; + } + + if (si_filep) + { + post_data.append("&"); + 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; + } + + if (gUserText.size()) + { + post_data.append("&"); + 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 dw_filep; + dw_filep = NULL; + + // Debugging spam +#if 0 + printf("Crash report post data:\n--------\n"); + printf("%s", post_data.getString()); + printf("\n--------\n"); +#endif + + // Send the report. Yes, it's this easy. + { + CURL *curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, "http://secondlife.com/cgi-bin/viewer_crash_reporter2"); + + llinfos << "Connecting to crash report server" << llendl; + CURLcode result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + + if(result != CURLE_OK) + { + llinfos << "Couldn't talk to crash report server" << llendl; + } + else + { + llinfos << "Response from crash report server:" << llendl; + llinfos << gServerResponse << llendl; + } + } + + return 0; +} + +LLFileEncoder::LLFileEncoder(const char *form_name, const char *filename, bool isCrashLog) +{ + mFormname = form_name; + mFilename = filename; + mIsValid = FALSE; + + int res; + + struct stat stat_data; + res = stat(mFilename.c_str(), &stat_data); + if (res) + { + llwarns << "File " << mFilename << " is missing!" << llendl; + return; + } + else + { + // Debugging spam +// llinfos << "File " << mFilename << " is present..." << llendl; + + if(!gCrashInPreviousExec && isCrashLog) + { + // Make sure the file isn't too old. + double age = difftime(gLaunchTime, stat_data.st_mtimespec.tv_sec); + +// llinfos << "age is " << age << llendl; + + if(age > 60.0) + { + // The file was last modified more than 60 seconds before the crash reporter was launched. Assume it's stale. + llwarns << "File " << mFilename << " is too old!" << llendl; + return; + } + } + + } + + S32 buf_size = stat_data.st_size; + FILE *fp = fopen(mFilename.c_str(), "rb"); + U8 *buf = new U8[buf_size + 1]; + fread(buf, 1, buf_size, fp); + fclose(fp); + buf[buf_size] = 0; + + mBuf = (char *)buf; + + if(isCrashLog) + { + // Crash logs consist of a number of entries, one per crash. + // Each entry is preceeded by "**********" on a line by itself. + // We want only the most recent (i.e. last) one. + const char *sep = "**********"; + const char *start = mBuf.c_str(); + const char *cur = start; + const char *temp = strstr(cur, sep); + + while(temp != NULL) + { + // Skip past the marker we just found + cur = temp + strlen(sep); + + // and try to find another + temp = strstr(cur, sep); + } + + // If there's more than one entry in the log file, strip all but the last one. + if(cur != start) + { + mBuf.erase(0, cur - start); + } + } + + mIsValid = TRUE; + delete[] buf; +} + +LLString LLFileEncoder::encodeURL(const S32 max_length) +{ + LLString result = mFormname; + result.append("="); + + S32 i = 0; + + if (max_length) + { + if (mBuf.size() > max_length) + { + i = mBuf.size() - max_length; + } + } + +#if 0 + // Plain text version for debugging + result.append(mBuf); +#else + // Not using LLString because of bad performance issues + S32 buf_size = mBuf.size(); + S32 url_buf_size = 3*mBuf.size() + 1; + char *url_buf = new char[url_buf_size]; + + S32 cur_pos = 0; + for (; i < buf_size; i++) + { + sprintf(url_buf + cur_pos, "%%%02x", mBuf[i]); + cur_pos += 3; + } + url_buf[i*3] = 0; + + result.append(url_buf); + delete[] url_buf; +#endif + return result; +} + +LLString encode_string(const char *formname, const LLString &str) +{ + LLString result = formname; + result.append("="); + // 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; +} |