mac_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 #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];             /* Flawfinder: ignore */
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                                                 // Get the user response text
00115                                                 err = GetControlData(textField, kControlNoPart, kControlEditTextTextTag, size, (Ptr)buffer, &size);
00116                                         }
00117                                         if(err == noErr)
00118                                         {
00119                                                 // Make sure the string is terminated.
00120                                                 buffer[size] = 0;
00121                                                 gUserText = buffer;
00122                                                 llinfos << buffer << llendl;
00123                                         }
00124                                         
00125                                         // Send the report.
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;                 // Maximum size to transmit of the Dr. Watson log file
00147         const S32 SL_MAX_SIZE = 100000;                 // Maximum size of the Second Life log file.
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                 // Delay five seconds to let CrashReporter do its thing.
00179                 sleep(5);
00180         }
00181                 
00182 #if 1
00183         // Real UI...
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                 // Set focus to the edit text area
00204                 ControlRef textField = NULL;
00205                 ControlID id;
00206 
00207                 id.signature = 'text';
00208                 id.id = 0;
00209                 
00210                 // Don't set err if any of this fails, since it's non-critical.
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                 // Set up an event handler for the window.
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         // Cheap version -- just use the standard system alert.
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                         &params,
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                 // Only send the report if the user agreed to it.
00297                 llinfos << "User cancelled, not sending report" << llendl;
00298 
00299                 return(0);
00300         }
00301 
00302         // We assume that all the logs we're looking for reside on the current drive
00303         gDirUtilp->initAppDirs("SecondLife");
00304 
00305         int res;
00306 
00307         // Lots of silly variable, replicated for each log file.
00308         LLString db_file_name;
00309         LLString sl_file_name;
00310         LLString dw_file_name; // DW file name is a hack for now...
00311         LLString st_file_name; // stats.log file
00312         LLString si_file_name; // settings.ini file
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         // We do the parsing for the debug_info file first, as that will
00323         // give us the location of the SecondLife.log file.
00324         //
00325 
00326         // Figure out the filename of the debug log
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         // Get the filename of the SecondLife.log file
00331 
00332         // *NOTE: changing the size of either of these buffers will
00333         // require changing the sscanf() format string to correctly
00334         // account for it.
00335         char tmp_sl_name[LL_MAX_PATH];  /* Flawfinder: ignore */
00336         tmp_sl_name[0] = '\0';
00337         char tmp_space[MAX_STRING];             /* Flawfinder: ignore */
00338         tmp_space[0] = '\0';
00339 
00340         // Look for it in the debug_info.log file
00341         if (db_filep->isValid())
00342         {
00343                 // This was originally scanning for "SL Log: %[^\r\n]", which happily skipped to the next line
00344                 // on debug logs (which don't have anything after "SL Log:" and tried to open a nonsensical filename.
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         // If we actually have a legitimate file name, use it.
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                 // Figure out the filename of the second life log
00366                 sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log").c_str();
00367         }
00368 
00369         // Now we get the SecondLife.log file if it's there, and recent enough...
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         // MBW -- This needs to go find "~/Library/Logs/CrashReporter/Second Life.crash.log" on 10.3
00394         // or "~/Library/Logs/Second Life.crash.log" on 10.2.
00395         {
00396                 char path[MAX_PATH];            /* Flawfinder: ignore */
00397                 FSRef folder;
00398                 
00399                 if(FSFindFolder(kUserDomain, kLogsFolderType, false, &folder) == noErr)
00400                 {
00401                         // folder is an FSRef to ~/Library/Logs/
00402                         if(FSRefMakePath(&folder, (UInt8*)&path, sizeof(path)) == noErr)
00403                         {
00404                                 struct stat dw_stat;
00405 //                              printf("path is %s\n", path);
00406                                 
00407                                 // Try the 10.3 path first...
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                                         // Try the 10.2 one next...
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         // Append the userserver
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         // Debugging spam
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         // Send the report.  Yes, it's this easy.
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                 // Debugging spam
00577 //              llinfos << "File " << mFilename << " is present..." << llendl;
00578 
00579                 if(!gCrashInPreviousExec && isCrashLog)
00580                 {
00581                         // Make sure the file isn't too old.
00582                         double age = difftime(gLaunchTime, stat_data.st_mtimespec.tv_sec);
00583 
00584 //                      llinfos << "age is " << age << llendl;
00585 
00586                         if(age > 60.0)
00587                         {
00588                                 // The file was last modified more than 60 seconds before the crash reporter was launched.  Assume it's stale.
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");              /* Flawfinder: ignore */
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                 // Crash logs consist of a number of entries, one per crash.
00608                 // Each entry is preceeded by "**********" on a line by itself.
00609                 // We want only the most recent (i.e. last) one.
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                         // Skip past the marker we just found
00618                         cur = temp + strlen(sep);               /* Flawfinder: ignore */
00619                         
00620                         // and try to find another
00621                         temp = strstr(cur, sep);
00622                 }
00623                 
00624                 // If there's more than one entry in the log file, strip all but the last one.
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         // Plain text version for debugging
00652         result.append(mBuf);
00653 #else
00654         // Not using LLString because of bad performance issues
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]);          /* Flawfinder: ignore */
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         // Not using LLString because of bad performance issues
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]);           /* Flawfinder: ignore */
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 }

Generated on Thu Jul 1 06:09:54 2010 for Second Life Viewer by  doxygen 1.4.7