555 lines
13 KiB
C++
555 lines
13 KiB
C++
/**
|
|
* @file linux_crash_logger.cpp
|
|
* @brief Linux crash logger implementation
|
|
*
|
|
* Copyright (c) 2003-$CurrentYear$, Linden Research, Inc.
|
|
* $License$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <curl/curl.h>
|
|
|
|
#if LL_GTK
|
|
# include "gtk/gtk.h"
|
|
#endif // LL_GTK
|
|
|
|
#include "indra_constants.h" // CRASH_BEHAVIOR_ASK
|
|
#include "llerror.h"
|
|
#include "lltimer.h"
|
|
#include "lldir.h"
|
|
|
|
#include "llstring.h"
|
|
|
|
|
|
// These need to be localized.
|
|
static const char dialog_text[] =
|
|
"Second Life appears to have crashed.\n"
|
|
"This crash reporter collects information about your computer's hardware, operating system, and some Second Life logs, which are used for debugging purposes only.\n"
|
|
"Sending crash reports is the best way to help us improve the quality of Second Life.\n"
|
|
"If you continue to experience this problem, please try one of the following:\n"
|
|
"- Contact support by email at support@lindenlab.com\n"
|
|
"- If you can log-in, please contact Live Help by using menu Help > Live Help.\n"
|
|
"- Search the Second Life Knowledge Base at http://secondlife.com/knowledgebase/\n"
|
|
"\n"
|
|
"Send crash report?";
|
|
|
|
static const char dialog_title[] =
|
|
"Second Life Crash Logger";
|
|
|
|
|
|
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);
|
|
|
|
LLString gServerResponse;
|
|
BOOL gSendReport = FALSE;
|
|
LLString gUserserver;
|
|
LLString gUserText;
|
|
BOOL gCrashInPreviousExec = FALSE;
|
|
time_t gLaunchTime;
|
|
|
|
static 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;
|
|
}
|
|
|
|
#if LL_GTK
|
|
static void response_callback (GtkDialog *dialog,
|
|
gint arg1,
|
|
gpointer user_data)
|
|
{
|
|
gint *response = (gint*)user_data;
|
|
*response = arg1;
|
|
gtk_widget_destroy(GTK_WIDGET(dialog));
|
|
gtk_main_quit();
|
|
}
|
|
#endif // LL_GTK
|
|
|
|
static BOOL do_ask_dialog(void)
|
|
{
|
|
#if LL_GTK
|
|
gtk_disable_setlocale();
|
|
if (!gtk_init_check(NULL, NULL)) {
|
|
llinfos << "Could not initialize GTK for 'ask to send crash report' dialog; not sending report." << llendl;
|
|
return FALSE;
|
|
}
|
|
|
|
GtkWidget *win = NULL;
|
|
GtkDialogFlags flags = GTK_DIALOG_MODAL;
|
|
GtkMessageType messagetype = GTK_MESSAGE_QUESTION;
|
|
GtkButtonsType buttons = GTK_BUTTONS_YES_NO;
|
|
gint response = GTK_RESPONSE_NONE;
|
|
|
|
win = gtk_message_dialog_new(NULL,
|
|
flags, messagetype, buttons,
|
|
dialog_text);
|
|
gtk_window_set_type_hint(GTK_WINDOW(win),
|
|
GDK_WINDOW_TYPE_HINT_DIALOG);
|
|
gtk_window_set_title(GTK_WINDOW(win), dialog_title);
|
|
g_signal_connect (win,
|
|
"response",
|
|
G_CALLBACK (response_callback),
|
|
&response);
|
|
gtk_widget_show_all (win);
|
|
gtk_main();
|
|
|
|
return (GTK_RESPONSE_OK == response ||
|
|
GTK_RESPONSE_YES == response ||
|
|
GTK_RESPONSE_APPLY == response);
|
|
#else
|
|
return FALSE;
|
|
#endif // LL_GTK
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const S32 BT_MAX_SIZE = 100000; // Maximum size to transmit of the backtrace file
|
|
const S32 SL_MAX_SIZE = 100000; // Maximum size of the Second Life log file.
|
|
int i;
|
|
S32 crash_behavior = CRASH_BEHAVIOR_ALWAYS_SEND;
|
|
|
|
time(&gLaunchTime);
|
|
|
|
llinfos << "Starting Second Life Viewer Crash Reporter" << llendl;
|
|
|
|
for(i=1; i<argc; i++)
|
|
{
|
|
if(!strcmp(argv[i], "-dialog"))
|
|
{
|
|
llinfos << "Show the user dialog" << llendl;
|
|
crash_behavior = CRASH_BEHAVIOR_ASK;
|
|
}
|
|
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)
|
|
{
|
|
// Wait a while to let the crashed client finish exiting,
|
|
// freeing up the screen/etc.
|
|
sleep(5);
|
|
}
|
|
|
|
// *FIX: do some dialog stuff here?
|
|
if (CRASH_BEHAVIOR_ALWAYS_SEND == crash_behavior)
|
|
{
|
|
gSendReport = TRUE;
|
|
}
|
|
else if (CRASH_BEHAVIOR_ASK == crash_behavior)
|
|
{
|
|
gSendReport = do_ask_dialog();
|
|
}
|
|
|
|
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");
|
|
|
|
// Lots of silly variable, replicated for each log file.
|
|
LLString db_file_name;
|
|
LLString sl_file_name;
|
|
LLString bt_file_name; // stack_trace.log file
|
|
LLString st_file_name; // stats.log file
|
|
LLString si_file_name; // settings.xml file
|
|
|
|
LLFileEncoder *db_filep = NULL;
|
|
LLFileEncoder *sl_filep = NULL;
|
|
LLFileEncoder *st_filep = NULL;
|
|
LLFileEncoder *bt_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
|
|
// *TODO tofu - get right MAX_PATH.
|
|
// *FIX: What's up with this? This #define just can't be safe.
|
|
#define MAX_PATH PATH_MAX
|
|
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 (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])
|
|
{
|
|
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.xml").c_str();
|
|
si_filep = new LLFileEncoder("SI", si_file_name.c_str());
|
|
if (!si_filep->isValid())
|
|
{
|
|
delete si_filep;
|
|
si_filep = NULL;
|
|
}
|
|
|
|
// encode this as if it were a 'Dr Watson' plain-text backtrace
|
|
bt_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stack_trace.log").c_str();
|
|
bt_filep = new LLFileEncoder("DW", bt_file_name.c_str());
|
|
if (!bt_filep->isValid())
|
|
{
|
|
delete bt_filep;
|
|
bt_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("&");
|
|
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 (bt_filep)
|
|
{
|
|
post_data.append("&");
|
|
tmp_url_buf = bt_filep->encodeURL(BT_MAX_SIZE);
|
|
post_data += tmp_url_buf;
|
|
llinfos << "Sending crash log file" << llendl;
|
|
}
|
|
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.xml 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 bt_filep;
|
|
bt_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_mtim.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 ((S32)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;
|
|
}
|