linux_crash_logger.cpp

Go to the documentation of this file.
00001 
00032 #include "linden_common.h"
00033 
00034 #include <sys/types.h>
00035 #include <sys/stat.h>
00036 #include <unistd.h>
00037 
00038 #include <curl/curl.h>
00039 
00040 #if LL_GTK
00041 # include "gtk/gtk.h"
00042 #endif // LL_GTK
00043 
00044 #include "indra_constants.h"    // CRASH_BEHAVIOR_ASK
00045 #include "llerror.h"
00046 #include "lltimer.h"
00047 #include "lldir.h"
00048 
00049 #include "llstring.h"
00050 
00051 
00052 // These need to be localized.
00053 static const char dialog_text[] =
00054 "Second Life appears to have crashed.\n"
00055 "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"
00056 "\n"
00057 "This is a third party version of the Second Life viewer provided by Dale Glass.\n"
00058 "It is unsupported by Linden Lab, and reports crashes to daleglass.net.\n"
00059 "Please don't contact Linden Lab for support, IM Dale Glass or send email to\n"
00060 "dale@daleglass.net instead."
00061 "\n"
00062 "Send crash report?";
00063 
00064 static const char dialog_title[] =
00065 "Second Life Crash Logger";
00066 
00067 
00068 class LLFileEncoder
00069 {
00070 public:
00071         LLFileEncoder(const char *formname, const char *filename, bool isCrashLog = false);
00072 
00073         BOOL isValid() const { return mIsValid; }
00074         LLString encodeURL(const S32 max_length = 0);
00075 public:
00076         BOOL mIsValid;
00077         LLString mFilename;
00078         LLString mFormname;
00079         LLString mBuf;
00080 };
00081 
00082 LLString encode_string(const char *formname, const LLString &str);
00083 
00084 LLString gServerResponse;
00085 BOOL gSendReport = FALSE;
00086 LLString gUserserver;
00087 LLString gUserText;
00088 BOOL gCrashInPreviousExec = FALSE;
00089 time_t gLaunchTime;
00090 
00091 static size_t curl_download_callback(void *data, size_t size, size_t nmemb,
00092                                      void *user_data)
00093 {
00094         S32 bytes = size * nmemb;
00095         char *cdata = (char *) data;
00096         for (int i =0; i < bytes; i += 1)
00097         {
00098                 gServerResponse += (cdata[i]);
00099         }
00100         return bytes;
00101 }
00102 
00103 #if LL_GTK
00104 static void response_callback (GtkDialog *dialog,
00105                                gint       arg1,
00106                                gpointer   user_data)
00107 {
00108         gint *response = (gint*)user_data;
00109         *response = arg1;
00110         gtk_widget_destroy(GTK_WIDGET(dialog));
00111         gtk_main_quit();
00112 }
00113 #endif // LL_GTK
00114 
00115 static BOOL do_ask_dialog(void)
00116 {
00117 #if LL_GTK
00118         gtk_disable_setlocale();
00119         if (!gtk_init_check(NULL, NULL)) {
00120                 llinfos << "Could not initialize GTK for 'ask to send crash report' dialog; not sending report." << llendl;
00121                 return FALSE;
00122         }
00123         
00124         GtkWidget *win = NULL;
00125         GtkDialogFlags flags = GTK_DIALOG_MODAL;
00126         GtkMessageType messagetype = GTK_MESSAGE_QUESTION;
00127         GtkButtonsType buttons = GTK_BUTTONS_YES_NO;
00128         gint response = GTK_RESPONSE_NONE;
00129 
00130         win = gtk_message_dialog_new(NULL,
00131                                      flags, messagetype, buttons,
00132                                      dialog_text);
00133         gtk_window_set_type_hint(GTK_WINDOW(win),
00134                                  GDK_WINDOW_TYPE_HINT_DIALOG);
00135         gtk_window_set_title(GTK_WINDOW(win), dialog_title);
00136         g_signal_connect (win,
00137                           "response", 
00138                           G_CALLBACK (response_callback),
00139                           &response);
00140         gtk_widget_show_all (win);
00141         gtk_main();
00142 
00143         return (GTK_RESPONSE_OK == response ||
00144                 GTK_RESPONSE_YES == response ||
00145                 GTK_RESPONSE_APPLY == response);
00146 #else
00147         return FALSE;
00148 #endif // LL_GTK
00149 }
00150 
00151 
00152 int main(int argc, char **argv)
00153 {
00154         const S32 BT_MAX_SIZE = 100000;                 // Maximum size to transmit of the backtrace file
00155         const S32 SL_MAX_SIZE = 100000;                 // Maximum size of the Second Life log file.
00156         int i;
00157         S32 crash_behavior = CRASH_BEHAVIOR_ALWAYS_SEND;
00158         
00159         time(&gLaunchTime);
00160         
00161         llinfos << "Starting Second Life Viewer Crash Reporter" << llendl;
00162         
00163         for(i=1; i<argc; i++)
00164         {
00165                 if(!strcmp(argv[i], "-dialog"))
00166                 {
00167                         llinfos << "Show the user dialog" << llendl;
00168                         crash_behavior = CRASH_BEHAVIOR_ASK;
00169                 }
00170                 if(!strcmp(argv[i], "-previous"))
00171                 {
00172                         gCrashInPreviousExec = TRUE;
00173                 }
00174                 if(!strcmp(argv[i], "-user"))
00175                 {
00176                         if ((i + 1) < argc)
00177                         {
00178                                 i++;
00179                                 gUserserver = argv[i];
00180                                 llinfos << "Got userserver " << gUserserver << llendl;
00181                         }
00182                 }
00183         }
00184         
00185         if( gCrashInPreviousExec )
00186         {
00187                 llinfos << "Previous execution did not remove SecondLife.exec_marker" << llendl;
00188         }
00189         
00190         if(!gCrashInPreviousExec)
00191         {
00192                 // Wait a while to let the crashed client finish exiting,
00193                 // freeing up the screen/etc.
00194                 sleep(5);
00195         }
00196 
00197         // *FIX: do some dialog stuff here?
00198         if (CRASH_BEHAVIOR_ALWAYS_SEND == crash_behavior)
00199         {
00200                 gSendReport = TRUE;
00201         }
00202         else if (CRASH_BEHAVIOR_ASK == crash_behavior)
00203         {
00204                 gSendReport = do_ask_dialog();
00205         }
00206         
00207         if(!gSendReport)
00208         {
00209                 // Only send the report if the user agreed to it.
00210                 llinfos << "User cancelled, not sending report" << llendl;
00211 
00212                 return(0);
00213         }
00214 
00215         // We assume that all the logs we're looking for reside on the current drive
00216         gDirUtilp->initAppDirs("SecondLife");
00217 
00218         // Lots of silly variable, replicated for each log file.
00219         LLString db_file_name;
00220         LLString sl_file_name;
00221         LLString bt_file_name; // stack_trace.log file
00222         LLString st_file_name; // stats.log file
00223         LLString si_file_name; // settings.xml file
00224 
00225         LLFileEncoder *db_filep = NULL;
00226         LLFileEncoder *sl_filep = NULL;
00227         LLFileEncoder *st_filep = NULL;
00228         LLFileEncoder *bt_filep = NULL;
00229         LLFileEncoder *si_filep = NULL;
00230 
00232         //
00233         // We do the parsing for the debug_info file first, as that will
00234         // give us the location of the SecondLife.log file.
00235         //
00236 
00237         // Figure out the filename of the debug log
00238         db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log").c_str();
00239         db_filep = new LLFileEncoder("DB", db_file_name.c_str());
00240 
00241         // Get the filename of the SecondLife.log file
00242         // *NOTE: These buffer sizes are hardcoded into a scanf() below.
00243         char tmp_sl_name[LL_MAX_PATH];
00244         tmp_sl_name[0] = '\0';
00245         char tmp_space[256];
00246         tmp_space[0] = '\0';
00247 
00248         // Look for it in the debug_info.log file
00249         if (db_filep->isValid())
00250         {
00251                 // This was originally scanning for "SL Log: %[^\r\n]", which happily skipped to the next line
00252                 // on debug logs (which don't have anything after "SL Log:" and tried to open a nonsensical filename.
00253                 sscanf(db_filep->mBuf.c_str(), "SL Log:%255[ ]%1023[^\r\n]", tmp_space, tmp_sl_name);
00254         }
00255         else
00256         {
00257                 delete db_filep;
00258                 db_filep = NULL;
00259         }
00260 
00261         // If we actually have a legitimate file name, use it.
00262         if (gCrashInPreviousExec)
00263         {
00264                 // If we froze, the crash log this time around isn't useful.
00265                 // Use the old one.
00266                 sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.old");
00267         }
00268         else if (tmp_sl_name[0])
00269         {
00270                 sl_file_name = tmp_sl_name;
00271                 llinfos << "Using log file from debug log: " << sl_file_name << llendl;
00272         }
00273         else
00274         {
00275                 // Figure out the filename of the second life log
00276                 sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log").c_str();
00277         }
00278 
00279         // Now we get the SecondLife.log file if it's there, and recent enough...
00280         sl_filep = new LLFileEncoder("SL", sl_file_name.c_str());
00281         if (!sl_filep->isValid())
00282         {
00283                 delete sl_filep;
00284                 sl_filep = NULL;
00285         }
00286 
00287         st_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log").c_str();
00288         st_filep = new LLFileEncoder("ST", st_file_name.c_str());
00289         if (!st_filep->isValid())
00290         {
00291                 delete st_filep;
00292                 st_filep = NULL;
00293         }
00294 
00295         si_file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml").c_str();
00296         si_filep = new LLFileEncoder("SI", si_file_name.c_str());
00297         if (!si_filep->isValid())
00298         {
00299                 delete si_filep;
00300                 si_filep = NULL;
00301         }
00302 
00303         // encode this as if it were a 'Dr Watson' plain-text backtrace
00304         bt_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stack_trace.log").c_str();
00305         bt_filep = new LLFileEncoder("DW", bt_file_name.c_str());
00306         if (!bt_filep->isValid())
00307         {
00308                 delete bt_filep;
00309                 bt_filep = NULL;
00310         }
00311 
00312         LLString post_data;
00313         LLString tmp_url_buf;
00314 
00315         // Append the userserver
00316         tmp_url_buf = encode_string("USER", gUserserver);
00317         post_data += tmp_url_buf;
00318         llinfos << "PostData:" << post_data << llendl;
00319 
00320         if (gCrashInPreviousExec)
00321         {
00322                 post_data.append("&");
00323                 tmp_url_buf = encode_string("EF", "Y");
00324                 post_data += tmp_url_buf;
00325         }
00326 
00327         if (db_filep)
00328         {
00329                 post_data.append("&");
00330                 tmp_url_buf = db_filep->encodeURL();
00331                 post_data += tmp_url_buf;
00332                 llinfos << "Sending DB log file" << llendl;
00333         }
00334         else
00335         {
00336                 llinfos << "Not sending DB log file" << llendl;
00337         }
00338 
00339         if (sl_filep)
00340         {
00341                 post_data.append("&");
00342                 tmp_url_buf = sl_filep->encodeURL(SL_MAX_SIZE);
00343                 post_data += tmp_url_buf;
00344                 llinfos << "Sending SL log file" << llendl;
00345         }
00346         else
00347         {
00348                 llinfos << "Not sending SL log file" << llendl;
00349         }
00350 
00351         if (st_filep)
00352         {
00353                 post_data.append("&");
00354                 tmp_url_buf = st_filep->encodeURL(SL_MAX_SIZE);
00355                 post_data += tmp_url_buf;
00356                 llinfos << "Sending stats log file" << llendl;
00357         }
00358         else
00359         {
00360                 llinfos << "Not sending stats log file" << llendl;
00361         }
00362 
00363         if (bt_filep)
00364         {
00365                 post_data.append("&");
00366                 tmp_url_buf = bt_filep->encodeURL(BT_MAX_SIZE);
00367                 post_data += tmp_url_buf;
00368                 llinfos << "Sending crash log file" << llendl;
00369         }
00370         else
00371         {
00372                 llinfos << "Not sending crash log file" << llendl;
00373         }
00374 
00375         if (si_filep)
00376         {
00377                 post_data.append("&");
00378                 tmp_url_buf = si_filep->encodeURL();
00379                 post_data += tmp_url_buf;
00380                 llinfos << "Sending settings log file" << llendl;
00381         }
00382         else
00383         {
00384                 llinfos << "Not sending settings.xml file" << llendl;
00385         }
00386 
00387         if (gUserText.size())
00388         {
00389                 post_data.append("&");
00390                 tmp_url_buf = encode_string("UN", gUserText);
00391                 post_data += tmp_url_buf;
00392         }
00393 
00394         delete db_filep;
00395         db_filep = NULL;
00396         delete sl_filep;
00397         sl_filep = NULL;
00398         delete bt_filep;
00399         bt_filep = NULL;
00400 
00401         // Debugging spam
00402 #if 0
00403         printf("Crash report post data:\n--------\n");
00404         printf("%s", post_data.getString());
00405         printf("\n--------\n");
00406 #endif
00407         
00408         // Send the report.  Yes, it's this easy.
00409         {
00410                 CURL *curl = curl_easy_init();
00411 
00412                 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
00413                 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback);
00414                 curl_easy_setopt(curl, CURLOPT_POST, 1); 
00415                 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str()); 
00416                 curl_easy_setopt(curl, CURLOPT_URL,     "http://sl.daleglass.net/viewer_crash_reporter");
00417                 
00418                 llinfos << "Connecting to crash report server" << llendl;
00419                 CURLcode result = curl_easy_perform(curl);
00420                 
00421                 curl_easy_cleanup(curl);
00422                 
00423                 if(result != CURLE_OK)
00424                 {
00425                         llinfos << "Couldn't talk to crash report server" << llendl;
00426                 }
00427                 else
00428                 {
00429                         llinfos << "Response from crash report server:" << llendl;
00430                         llinfos << gServerResponse << llendl;                   
00431                 }
00432         }
00433         
00434         return 0;
00435 }
00436 
00437 LLFileEncoder::LLFileEncoder(const char *form_name, const char *filename, bool isCrashLog)
00438 {
00439         mFormname = form_name;
00440         mFilename = filename;
00441         mIsValid = FALSE;
00442 
00443         int res;
00444         
00445         struct stat stat_data;
00446         res = stat(mFilename.c_str(), &stat_data);
00447         if (res)
00448         {
00449                 llwarns << "File " << mFilename << " is missing!" << llendl;
00450                 return;
00451         }
00452         else
00453         {
00454                 // Debugging spam
00455 //              llinfos << "File " << mFilename << " is present..." << llendl;
00456 
00457                 if(!gCrashInPreviousExec && isCrashLog)
00458                 {
00459                         // Make sure the file isn't too old.
00460                         double age = difftime(gLaunchTime, stat_data.st_mtim.tv_sec);
00461 
00462 //                      llinfos << "age is " << age << llendl;
00463 
00464                         if(age > 60.0)
00465                         {
00466                                 // The file was last modified more than 60 seconds before the crash reporter was launched.  Assume it's stale.
00467                                 llwarns << "File " << mFilename << " is too old!" << llendl;
00468                                 return;
00469                         }
00470                 }
00471 
00472         }
00473 
00474         S32 buf_size = stat_data.st_size;
00475         FILE *fp = fopen(mFilename.c_str(), "rb");
00476         U8 *buf = new U8[buf_size + 1];
00477         size_t nread = fread(buf, 1, buf_size, fp);
00478         fclose(fp);
00479         buf[nread] = 0;
00480 
00481         mBuf = (char *)buf;
00482         
00483         if(isCrashLog)
00484         {
00485                 // Crash logs consist of a number of entries, one per crash.
00486                 // Each entry is preceeded by "**********" on a line by itself.
00487                 // We want only the most recent (i.e. last) one.
00488                 const char *sep = "**********";
00489                 const char *start = mBuf.c_str();
00490                 const char *cur = start;
00491                 const char *temp = strstr(cur, sep);
00492                 
00493                 while(temp != NULL)
00494                 {
00495                         // Skip past the marker we just found
00496                         cur = temp + strlen(sep);
00497                         
00498                         // and try to find another
00499                         temp = strstr(cur, sep);
00500                 }
00501                 
00502                 // If there's more than one entry in the log file, strip all but the last one.
00503                 if(cur != start)
00504                 {
00505                         mBuf.erase(0, cur - start);
00506                 }
00507         }
00508 
00509         mIsValid = TRUE;
00510         delete[] buf;
00511 }
00512 
00513 LLString LLFileEncoder::encodeURL(const S32 max_length)
00514 {
00515         LLString result = mFormname;
00516         result.append("=");
00517 
00518         S32 i = 0;
00519 
00520         if (max_length)
00521         {
00522                 if ((S32)mBuf.size() > max_length)
00523                 {
00524                         i = mBuf.size() - max_length;
00525                 }
00526         }
00527 
00528 #if 0
00529         // Plain text version for debugging
00530         result.append(mBuf);
00531 #else
00532         // Not using LLString because of bad performance issues
00533         S32 buf_size = mBuf.size();
00534         S32 url_buf_size = 3*mBuf.size() + 1;
00535         char *url_buf = new char[url_buf_size];
00536 
00537         S32 cur_pos = 0;
00538         for (; i < buf_size; i++)
00539         {
00540                 sprintf(url_buf + cur_pos, "%%%02x", mBuf[i]);
00541                 cur_pos += 3;
00542         }
00543         url_buf[i*3] = 0;
00544 
00545         result.append(url_buf);
00546         delete[] url_buf;
00547 #endif
00548         return result;
00549 }
00550 
00551 LLString encode_string(const char *formname, const LLString &str)
00552 {
00553         LLString result = formname;
00554         result.append("=");
00555         // Not using LLString because of bad performance issues
00556         S32 buf_size = str.size();
00557         S32 url_buf_size = 3*str.size() + 1;
00558         char *url_buf = new char[url_buf_size];
00559 
00560         S32 cur_pos = 0;
00561         S32 i;
00562         for (i = 0; i < buf_size; i++)
00563         {
00564                 sprintf(url_buf + cur_pos, "%%%02x", str[i]);
00565                 cur_pos += 3;
00566         }
00567         url_buf[i*3] = 0;
00568 
00569         result.append(url_buf);
00570         delete[] url_buf;
00571         return result;
00572 }

Generated on Thu Jul 1 06:08:17 2010 for Second Life Viewer by  doxygen 1.4.7