summaryrefslogtreecommitdiff
path: root/indra/mac_crash_logger
diff options
context:
space:
mode:
Diffstat (limited to 'indra/mac_crash_logger')
-rw-r--r--indra/mac_crash_logger/mac_crash_logger.cpp669
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"),
+ &params,
+ &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;
+}