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 #include "llerror.h"
00041 #include "lltimer.h"
00042 #include "lldir.h"
00043
00044 #include "llstring.h"
00045
00046 class LLFileEncoder
00047 {
00048 public:
00049 LLFileEncoder(const char *formname, const char *filename, bool isCrashLog = false);
00050
00051 BOOL isValid() const { return mIsValid; }
00052 LLString encodeURL(const S32 max_length = 0);
00053 public:
00054 BOOL mIsValid;
00055 LLString mFilename;
00056 LLString mFormname;
00057 LLString mBuf;
00058 };
00059
00060 LLString encode_string(const char *formname, const LLString &str);
00061
00062 #include <Carbon/Carbon.h>
00063
00064 LLString gServerResponse;
00065 BOOL gSendReport = FALSE;
00066 LLString gUserserver;
00067 LLString gUserText;
00068 WindowRef gWindow = NULL;
00069 EventHandlerRef gEventHandler = NULL;
00070 BOOL gCrashInPreviousExec = FALSE;
00071 time_t gLaunchTime;
00072
00073 size_t curl_download_callback(void *data, size_t size, size_t nmemb,
00074 void *user_data)
00075 {
00076 S32 bytes = size * nmemb;
00077 char *cdata = (char *) data;
00078 for (int i =0; i < bytes; i += 1)
00079 {
00080 gServerResponse += (cdata[i]);
00081 }
00082 return bytes;
00083 }
00084
00085 OSStatus dialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata)
00086 {
00087 OSStatus result = eventNotHandledErr;
00088 OSStatus err;
00089 UInt32 evtClass = GetEventClass(event);
00090 UInt32 evtKind = GetEventKind(event);
00091
00092 if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess))
00093 {
00094 HICommand cmd;
00095 err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd);
00096
00097 if(err == noErr)
00098 {
00099 switch(cmd.commandID)
00100 {
00101 case kHICommandOK:
00102 {
00103 char buffer[65535];
00104 Size size = sizeof(buffer) - 1;
00105 ControlRef textField = NULL;
00106 ControlID id;
00107
00108 id.signature = 'text';
00109 id.id = 0;
00110
00111 err = GetControlByID(gWindow, &id, &textField);
00112 if(err == noErr)
00113 {
00114
00115 err = GetControlData(textField, kControlNoPart, kControlEditTextTextTag, size, (Ptr)buffer, &size);
00116 }
00117 if(err == noErr)
00118 {
00119
00120 buffer[size] = 0;
00121 gUserText = buffer;
00122 llinfos << buffer << llendl;
00123 }
00124
00125
00126 gSendReport = TRUE;
00127
00128 QuitAppModalLoopForWindow(gWindow);
00129 result = noErr;
00130 }
00131 break;
00132
00133 case kHICommandCancel:
00134 QuitAppModalLoopForWindow(gWindow);
00135 result = noErr;
00136 break;
00137 }
00138 }
00139 }
00140
00141 return(result);
00142 }
00143
00144 int main(int argc, char **argv)
00145 {
00146 const S32 DW_MAX_SIZE = 100000;
00147 const S32 SL_MAX_SIZE = 100000;
00148 int i;
00149
00150 time(&gLaunchTime);
00151
00152 llinfos << "Starting Second Life Viewer Crash Reporter" << llendl;
00153
00154 for(i=1; i<argc; i++)
00155 {
00156 if(!strcmp(argv[i], "-previous"))
00157 {
00158 gCrashInPreviousExec = TRUE;
00159 }
00160 if(!strcmp(argv[i], "-user"))
00161 {
00162 if ((i + 1) < argc)
00163 {
00164 i++;
00165 gUserserver = argv[i];
00166 llinfos << "Got userserver " << gUserserver << llendl;
00167 }
00168 }
00169 }
00170
00171 if( gCrashInPreviousExec )
00172 {
00173 llinfos << "Previous execution did not remove SecondLife.exec_marker" << llendl;
00174 }
00175
00176 if(!gCrashInPreviousExec)
00177 {
00178
00179 sleep(5);
00180 }
00181
00182 #if 1
00183
00184 OSStatus err;
00185 IBNibRef nib = NULL;
00186
00187 err = CreateNibReference(CFSTR("CrashReporter"), &nib);
00188
00189 if(err == noErr)
00190 {
00191 if(gCrashInPreviousExec)
00192 {
00193 err = CreateWindowFromNib(nib, CFSTR("CrashReporterDelayed"), &gWindow);
00194 }
00195 else
00196 {
00197 err = CreateWindowFromNib(nib, CFSTR("CrashReporter"), &gWindow);
00198 }
00199 }
00200
00201 if(err == noErr)
00202 {
00203
00204 ControlRef textField = NULL;
00205 ControlID id;
00206
00207 id.signature = 'text';
00208 id.id = 0;
00209
00210
00211 if(GetControlByID(gWindow, &id, &textField) == noErr)
00212 {
00213 SetKeyboardFocus(gWindow, textField, kControlFocusNextPart);
00214 }
00215 }
00216
00217 if(err == noErr)
00218 {
00219 ShowWindow(gWindow);
00220 }
00221
00222 if(err == noErr)
00223 {
00224
00225 EventTypeSpec handlerEvents[] =
00226 {
00227 { kEventClassCommand, kEventCommandProcess }
00228 };
00229
00230 InstallWindowEventHandler(
00231 gWindow,
00232 NewEventHandlerUPP(dialogHandler),
00233 GetEventTypeCount (handlerEvents),
00234 handlerEvents,
00235 0,
00236 &gEventHandler);
00237 }
00238
00239 if(err == noErr)
00240 {
00241 RunAppModalLoopForWindow(gWindow);
00242 }
00243
00244 if(gWindow != NULL)
00245 {
00246 DisposeWindow(gWindow);
00247 }
00248
00249 if(nib != NULL)
00250 {
00251 DisposeNibReference(nib);
00252 }
00253 #else
00254
00255 SInt16 itemHit = 0;
00256 AlertStdCFStringAlertParamRec params;
00257 OSStatus err = noErr;
00258 DialogRef alert = NULL;
00259
00260 params.version = kStdCFStringAlertVersionOne;
00261 params.movable = false;
00262 params.helpButton = false;
00263 params.defaultText = CFSTR("Send Report");
00264 params.cancelText = CFSTR("Don't Send Report");
00265 params.otherText = 0;
00266 params.defaultButton = kAlertStdAlertOKButton;
00267 params.cancelButton = kAlertStdAlertCancelButton;
00268 params.position = kWindowDefaultPosition;
00269 params.flags = 0;
00270
00271 err = CreateStandardAlert(
00272 kAlertCautionAlert,
00273 CFSTR("Second Life appears to have crashed."),
00274 CFSTR(
00275 "This is a third party viewer provided by Dale Glass and unsupported by Linden Lab.\r"
00276 "Since I don't own any Mac hardware, I can only offer limited support on this platform."
00277 "Nevertheless, you're welcome to contact me by sending an IM to Dale Glass, or email"
00278 "to dale@daleglass.net. I can't guarantee that I'll be able to help you, though."),
00279 ¶ms,
00280 &alert);
00281
00282 if(err == noErr)
00283 {
00284 err = RunStandardAlert(
00285 alert,
00286 NULL,
00287 &itemHit);
00288 }
00289
00290 if(itemHit == kAlertStdAlertOKButton)
00291 gSendReport = TRUE;
00292 #endif
00293
00294 if(!gSendReport)
00295 {
00296
00297 llinfos << "User cancelled, not sending report" << llendl;
00298
00299 return(0);
00300 }
00301
00302
00303 gDirUtilp->initAppDirs("SecondLife");
00304
00305 int res;
00306
00307
00308 LLString db_file_name;
00309 LLString sl_file_name;
00310 LLString dw_file_name;
00311 LLString st_file_name;
00312 LLString si_file_name;
00313
00314 LLFileEncoder *db_filep = NULL;
00315 LLFileEncoder *sl_filep = NULL;
00316 LLFileEncoder *st_filep = NULL;
00317 LLFileEncoder *dw_filep = NULL;
00318 LLFileEncoder *si_filep = NULL;
00319
00321
00322
00323
00324
00325
00326
00327 db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log").c_str();
00328 db_filep = new LLFileEncoder("DB", db_file_name.c_str());
00329
00330
00331
00332
00333
00334
00335 char tmp_sl_name[LL_MAX_PATH];
00336 tmp_sl_name[0] = '\0';
00337 char tmp_space[MAX_STRING];
00338 tmp_space[0] = '\0';
00339
00340
00341 if (db_filep->isValid())
00342 {
00343
00344
00345 sscanf(
00346 db_filep->mBuf.c_str(),
00347 "SL Log:%254[ ]%1023[^\r\n]",
00348 tmp_space,
00349 tmp_sl_name);
00350 }
00351 else
00352 {
00353 delete db_filep;
00354 db_filep = NULL;
00355 }
00356
00357
00358 if (tmp_sl_name[0])
00359 {
00360 sl_file_name = tmp_sl_name;
00361 llinfos << "Using log file from debug log " << sl_file_name << llendl;
00362 }
00363 else
00364 {
00365
00366 sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log").c_str();
00367 }
00368
00369
00370 sl_filep = new LLFileEncoder("SL", sl_file_name.c_str());
00371 if (!sl_filep->isValid())
00372 {
00373 delete sl_filep;
00374 sl_filep = NULL;
00375 }
00376
00377 st_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log").c_str();
00378 st_filep = new LLFileEncoder("ST", st_file_name.c_str());
00379 if (!st_filep->isValid())
00380 {
00381 delete st_filep;
00382 st_filep = NULL;
00383 }
00384
00385 si_file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.ini").c_str();
00386 si_filep = new LLFileEncoder("SI", si_file_name.c_str());
00387 if (!si_filep->isValid())
00388 {
00389 delete si_filep;
00390 si_filep = NULL;
00391 }
00392
00393
00394
00395 {
00396 char path[MAX_PATH];
00397 FSRef folder;
00398
00399 if(FSFindFolder(kUserDomain, kLogsFolderType, false, &folder) == noErr)
00400 {
00401
00402 if(FSRefMakePath(&folder, (UInt8*)&path, sizeof(path)) == noErr)
00403 {
00404 struct stat dw_stat;
00405
00406
00407
00408 dw_file_name = LLString(path) + LLString("/CrashReporter/Second Life.crash.log");
00409 res = stat(dw_file_name.c_str(), &dw_stat);
00410
00411 if (res)
00412 {
00413
00414 dw_file_name = LLString(path) + LLString("/Second Life.crash.log");
00415 res = stat(dw_file_name.c_str(), &dw_stat);
00416 }
00417
00418 if (!res)
00419 {
00420 dw_filep = new LLFileEncoder("DW", dw_file_name.c_str(), true);
00421 if (!dw_filep->isValid())
00422 {
00423 delete dw_filep;
00424 dw_filep = NULL;
00425 }
00426 }
00427 else
00428 {
00429 llwarns << "Couldn't find any CrashReporter files..." << llendl;
00430 }
00431 }
00432 }
00433 }
00434
00435 LLString post_data;
00436 LLString tmp_url_buf;
00437
00438
00439 tmp_url_buf = encode_string("USER", gUserserver);
00440 post_data += tmp_url_buf;
00441 llinfos << "PostData:" << post_data << llendl;
00442
00443 if (gCrashInPreviousExec)
00444 {
00445 post_data.append("&");
00446 tmp_url_buf = encode_string("EF", "Y");
00447 post_data += tmp_url_buf;
00448 }
00449
00450 if (db_filep)
00451 {
00452 post_data.append("&");
00453 tmp_url_buf = db_filep->encodeURL();
00454 post_data += tmp_url_buf;
00455 llinfos << "Sending DB log file" << llendl;
00456 }
00457 else
00458 {
00459 llinfos << "Not sending DB log file" << llendl;
00460 }
00461
00462 if (sl_filep)
00463 {
00464 post_data.append("&");
00465 tmp_url_buf = sl_filep->encodeURL(SL_MAX_SIZE);
00466 post_data += tmp_url_buf;
00467 llinfos << "Sending SL log file" << llendl;
00468 }
00469 else
00470 {
00471 llinfos << "Not sending SL log file" << llendl;
00472 }
00473
00474 if (st_filep)
00475 {
00476 post_data.append("&");
00477 tmp_url_buf = st_filep->encodeURL(SL_MAX_SIZE);
00478 post_data += tmp_url_buf;
00479 llinfos << "Sending stats log file" << llendl;
00480 }
00481 else
00482 {
00483 llinfos << "Not sending stats log file" << llendl;
00484 }
00485
00486 if (dw_filep)
00487 {
00488 post_data.append("&");
00489 tmp_url_buf = dw_filep->encodeURL(DW_MAX_SIZE);
00490 post_data += tmp_url_buf;
00491 }
00492 else
00493 {
00494 llinfos << "Not sending crash log file" << llendl;
00495 }
00496
00497 if (si_filep)
00498 {
00499 post_data.append("&");
00500 tmp_url_buf = si_filep->encodeURL();
00501 post_data += tmp_url_buf;
00502 llinfos << "Sending settings log file" << llendl;
00503 }
00504 else
00505 {
00506 llinfos << "Not sending settings.ini file" << llendl;
00507 }
00508
00509 if (gUserText.size())
00510 {
00511 post_data.append("&");
00512 tmp_url_buf = encode_string("UN", gUserText);
00513 post_data += tmp_url_buf;
00514 }
00515
00516 delete db_filep;
00517 db_filep = NULL;
00518 delete sl_filep;
00519 sl_filep = NULL;
00520 delete dw_filep;
00521 dw_filep = NULL;
00522
00523
00524 #if 0
00525 printf("Crash report post data:\n--------\n");
00526 printf("%s", post_data.getString());
00527 printf("\n--------\n");
00528 #endif
00529
00530
00531 {
00532 CURL *curl = curl_easy_init();
00533
00534 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
00535 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback);
00536 curl_easy_setopt(curl, CURLOPT_POST, 1);
00537 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());
00538 curl_easy_setopt(curl, CURLOPT_URL, "http://sl.daleglass.net/viewer_crash_reporter");
00539
00540 llinfos << "Connecting to crash report server" << llendl;
00541 CURLcode result = curl_easy_perform(curl);
00542
00543 curl_easy_cleanup(curl);
00544
00545 if(result != CURLE_OK)
00546 {
00547 llinfos << "Couldn't talk to crash report server" << llendl;
00548 }
00549 else
00550 {
00551 llinfos << "Response from crash report server:" << llendl;
00552 llinfos << gServerResponse << llendl;
00553 }
00554 }
00555
00556 return 0;
00557 }
00558
00559 LLFileEncoder::LLFileEncoder(const char *form_name, const char *filename, bool isCrashLog)
00560 {
00561 mFormname = form_name;
00562 mFilename = filename;
00563 mIsValid = FALSE;
00564
00565 int res;
00566
00567 struct stat stat_data;
00568 res = stat(mFilename.c_str(), &stat_data);
00569 if (res)
00570 {
00571 llwarns << "File " << mFilename << " is missing!" << llendl;
00572 return;
00573 }
00574 else
00575 {
00576
00577
00578
00579 if(!gCrashInPreviousExec && isCrashLog)
00580 {
00581
00582 double age = difftime(gLaunchTime, stat_data.st_mtimespec.tv_sec);
00583
00584
00585
00586 if(age > 60.0)
00587 {
00588
00589 llwarns << "File " << mFilename << " is too old!" << llendl;
00590 return;
00591 }
00592 }
00593
00594 }
00595
00596 S32 buf_size = stat_data.st_size;
00597 FILE* fp = fopen(mFilename.c_str(), "rb");
00598 U8 *buf = new U8[buf_size + 1];
00599 fread(buf, 1, buf_size, fp);
00600 fclose(fp);
00601 buf[buf_size] = 0;
00602
00603 mBuf = (char *)buf;
00604
00605 if(isCrashLog)
00606 {
00607
00608
00609
00610 const char *sep = "**********";
00611 const char *start = mBuf.c_str();
00612 const char *cur = start;
00613 const char *temp = strstr(cur, sep);
00614
00615 while(temp != NULL)
00616 {
00617
00618 cur = temp + strlen(sep);
00619
00620
00621 temp = strstr(cur, sep);
00622 }
00623
00624
00625 if(cur != start)
00626 {
00627 mBuf.erase(0, cur - start);
00628 }
00629 }
00630
00631 mIsValid = TRUE;
00632 delete[] buf;
00633 }
00634
00635 LLString LLFileEncoder::encodeURL(const S32 max_length)
00636 {
00637 LLString result = mFormname;
00638 result.append("=");
00639
00640 S32 i = 0;
00641
00642 if (max_length)
00643 {
00644 if (mBuf.size() > max_length)
00645 {
00646 i = mBuf.size() - max_length;
00647 }
00648 }
00649
00650 #if 0
00651
00652 result.append(mBuf);
00653 #else
00654
00655 S32 buf_size = mBuf.size();
00656 S32 url_buf_size = 3*mBuf.size() + 1;
00657 char *url_buf = new char[url_buf_size];
00658 if (url_buf == NULL)
00659 {
00660 llerrs << "Memory Allocation Failed" << llendl;
00661 return result;
00662 }
00663 S32 cur_pos = 0;
00664 for (; i < buf_size; i++)
00665 {
00666 sprintf(url_buf + cur_pos, "%%%02x", mBuf[i]);
00667 cur_pos += 3;
00668 }
00669 url_buf[i*3] = 0;
00670
00671 result.append(url_buf);
00672 delete[] url_buf;
00673 #endif
00674 return result;
00675 }
00676
00677 LLString encode_string(const char *formname, const LLString &str)
00678 {
00679 LLString result = formname;
00680 result.append("=");
00681
00682 S32 buf_size = str.size();
00683 S32 url_buf_size = 3*str.size() + 1;
00684 char *url_buf = new char[url_buf_size];
00685 if (url_buf == NULL)
00686 {
00687 llerrs << "Memory Allocation Failed" << llendl;
00688 return result;
00689 }
00690
00691 S32 cur_pos = 0;
00692 S32 i;
00693 for (i = 0; i < buf_size; i++)
00694 {
00695 sprintf(url_buf + cur_pos, "%%%02x", str[i]);
00696 cur_pos += 3;
00697 }
00698 url_buf[i*3] = 0;
00699
00700 result.append(url_buf);
00701 delete[] url_buf;
00702 return result;
00703 }