Vidalia 0.2.12

CrashReporter.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 */
00010 /*
00011 **  The append_string() function in this file is derived from the implementation
00012 **  of strlcat() by Todd C. Miller. It is licensed as follows:
00013 **
00014 **  Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
00015 **  All rights reserved.
00016 **
00017 **  Redistribution and use in source and binary forms, with or without
00018 **  modification, are permitted provided that the following conditions
00019 **  are met:
00020 **  1. Redistributions of source code must retain the above copyright
00021 **     notice, this list of conditions and the following disclaimer.
00022 **  2. Redistributions in binary form must reproduce the above copyright
00023 **     notice, this list of conditions and the following disclaimer in the
00024 **     documentation and/or other materials provided with the distribution.
00025 **  3. The name of the author may not be used to endorse or promote products
00026 **     derived from this software without specific prior written permission.
00027 **
00028 **  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
00029 **  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
00030 **  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
00031 **  THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00032 **  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00033 **  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
00034 **  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
00035 **  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
00036 **  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
00037 **  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00038 */
00039 
00040 /*
00041 ** \file CrashReporter.h
00042 ** \brief General routines to install a Breakpad-based exception handler and
00043 ** set options related to launching the crash reporting application.
00044 */
00045 
00046 #include "CrashReporter.h"
00047 #include "stringutil.h"
00048 
00049 #if defined(Q_OS_WIN32)
00050 #include <client/windows/handler/exception_handler.h>
00051 #elif defined(Q_OS_MAC)
00052 #include <client/mac/handler/exception_handler.h>
00053 #elif defined(Q_OS_LINUX)
00054 #include <client/linux/handler/exception_handler.h>
00055 #elif defined(Q_OS_SOLARIS)
00056 #include <client/solaris/handler/exception_handler.h>
00057 #endif
00058 
00059 #include <QString>
00060 #include <QStringList>
00061 #include <QFileInfo>
00062 #include <QDir>
00063 
00064 #include <time.h>
00065 
00066 
00067 namespace CrashReporter
00068 {
00069 #if defined(Q_OS_WIN32)
00070 typedef wchar_t         _char_t;
00071 typedef HANDLE          _file_handle_t;
00072 #define PATH_SEPARATOR  TEXT("\\")
00073 # ifdef _USE_32BIT_TIME_T
00074 #   define TIME_TO_STRING(buf, buflen, t) \
00075       _ltoa_s(t, buf, buflen, 10)
00076 # else
00077 #   define TIME_TO_STRING(buf, buflen, t) \
00078       _i64toa_s(t, buf, buflen, 10)
00079 # endif
00080 #else
00081 typedef char            _char_t;
00082 typedef int             _file_handle_t;
00083 #define PATH_SEPARATOR  "/"
00084 #define TEXT(x)         (x)
00085 #define TIME_TO_STRING(buf, buflen, t) \
00086   snprintf(buf, buflen, "%ld", t)
00087 #endif
00088 
00089 /** Pointer to the Breakpad-installed exception handler called if Vidalia
00090  * crashes.
00091  * \sa install_exception_handler()
00092  */
00093 static google_breakpad::ExceptionHandler *exceptionHandler = 0;
00094 
00095 /** If true, the crash reporting application will be displayed when the
00096  * Breakpad-installed exception handler is called. Otherwise, the system
00097  * will handle the exception itself.
00098  * \sa set_crash_reporter()
00099  */
00100 static bool showCrashReporter = false;
00101 
00102 /** Absolute path of the crash reporting application that will be launched
00103  * from the exception handler.
00104  * \sa set_crash_reporter()
00105  */
00106 static _char_t crashReporterExecutable[MAX_PATH_LEN + 1] = TEXT("");
00107 
00108 /** Version information for the application being monitored for crashes.
00109  * The version will be written to the extra information file alongside
00110  * the minidump.
00111  */
00112 static char buildVersion[MAX_VERSION_LEN + 1] = "";
00113 
00114 /** Path and filename of the application to restart after displaying
00115  * the crash reporting dialog. The contents of this string are encoded
00116  * in UTF-8.
00117  * \sa set_restart_options()
00118  */
00119 static char restartExecutable[MAX_CMD_LEN + 1] = "";
00120 
00121 /** Additional arguments to use when restarting the crashed application.
00122  * The contents of this string are encoded in UTF-8.
00123  * \sa set_restart_options()
00124  */
00125 static char restartExecutableArgs[MAX_CMD_LEN + 1] = "";
00126 
00127 
00128 /** Records the time at which install_exception_handler() is called, which
00129  * is usually as early as possible during application startup. This is used
00130  * in minidump_callback() to determine how long the application was running
00131  * before it crashed.
00132  * \sa install_exception_handler()
00133  * \sa minidump_callback()
00134  */
00135 static time_t startupTime = 0;
00136 
00137 
00138 /** Slightly modified version of the strlcat() implementation by Todd C. 
00139  * Miller (see the top of this file or the LICENSE file for license details),
00140  * that supports arguments of either wchar_t* on Windows or the usual char*
00141  * everywhere else but retains the semantics of strlcat().
00142  */
00143 static size_t
00144 append_string(_char_t *dst, const _char_t *src, size_t siz)
00145 {
00146   _char_t *d = dst;
00147   const _char_t *s = src;
00148   size_t n = siz;
00149   size_t dlen;
00150 
00151   /* Find the end of dst and adjust bytes left but don't go past end */
00152   while (n-- != 0 && *d != TEXT('\0'))
00153     d++;
00154   dlen = d - dst;
00155   n = siz - dlen;
00156 
00157   if (n == 0)
00158 #if defined(Q_OS_WIN32)
00159     return (dlen + wcslen(s));
00160 #else
00161     return(dlen + strlen(s));
00162 #endif
00163 
00164   while (*s != TEXT('\0')) {
00165     if (n != 1) {
00166       *d++ = *s;
00167       n--;
00168     }
00169     s++;
00170   }
00171   *d = TEXT('\0');
00172 
00173   return(dlen + (s - src));     /* count does not include NUL */
00174 }
00175 
00176 /** Writes the formatted string "<b>key</b>=</b>val\n" to the file handle
00177  * specified by <b>hFile</b>. On Windows, <b>hFile</b> is a HANDLE. Everywhere
00178  * else, <b>hFile</b> is an int.
00179  */
00180 static void
00181 write_keyval_to_file(_file_handle_t hFile, const char *key, const char *val)
00182 {
00183 #if defined(Q_OS_WIN32)
00184   DWORD dwWritten;
00185   WriteFile(hFile, key, strlen(key), &dwWritten, NULL);
00186   WriteFile(hFile, "=", 1, &dwWritten, NULL);
00187   WriteFile(hFile, val, strlen(val), &dwWritten, NULL);
00188   WriteFile(hFile, "\n", 1, &dwWritten, NULL);
00189 #else
00190   write(hFile, key, strlen(key));
00191   write(hFile, "=", 1);
00192   write(hFile, val, strlen(val));
00193   write(hFile, "\n", 1);
00194 #endif
00195 }
00196 
00197 /** Writes to a file extra information used by the crash reporting
00198  * application such as how long the application was running before it
00199  * crashed, the application to restart, as well as any extra arguments.
00200  * The contents of the file are formatted as a series of "Key=Val\n" pairs.
00201  * The written file has the same path and base filename as the minidump
00202  * file, with ".info" appended to the end. Returns true if the file was
00203  * created succesfully. Otherwise, returns false.
00204  */
00205 static bool
00206 write_extra_dump_info(const _char_t *path, const _char_t *id, time_t crashTime)
00207 {
00208   static const char *KeyBuildVersion = "BuildVersion";
00209   static const char *KeyCrashTime = "CrashTime";
00210   static const char *KeyStartupTime = "StartupTime";
00211   static const char *KeyRestartExecutable = "RestartExecutable";
00212   static const char *KeyRestartExecutableArgs = "RestartExecutableArgs";
00213 
00214   _char_t extraInfoPath[MAX_PATH_LEN] = TEXT("");
00215   append_string(extraInfoPath, path, MAX_PATH_LEN);
00216   append_string(extraInfoPath, PATH_SEPARATOR, MAX_PATH_LEN);
00217   append_string(extraInfoPath, id, MAX_PATH_LEN);
00218   size_t len = append_string(extraInfoPath, TEXT(".dmp.info"), MAX_PATH_LEN);
00219   if (len >= MAX_PATH_LEN)
00220     return false;
00221 
00222 #if defined(Q_OS_WIN32)
00223   HANDLE hFile = CreateFile(extraInfoPath, GENERIC_WRITE, 0, NULL,
00224                             CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
00225   if (hFile == INVALID_HANDLE_VALUE)
00226     return false;
00227 #else
00228   /* TODO: Implement for non-Windowses */
00229 #endif
00230   
00231   char crashTimeString[24], startupTimeString[24];
00232   TIME_TO_STRING(crashTimeString, 24, crashTime);
00233   TIME_TO_STRING(startupTimeString, 24, startupTime);
00234 
00235   write_keyval_to_file(hFile, KeyBuildVersion, buildVersion);
00236   write_keyval_to_file(hFile, KeyCrashTime, crashTimeString);
00237   write_keyval_to_file(hFile, KeyStartupTime, startupTimeString);
00238   write_keyval_to_file(hFile, KeyRestartExecutable, restartExecutable);
00239   write_keyval_to_file(hFile, KeyRestartExecutableArgs, restartExecutableArgs);
00240 
00241 #if defined(Q_OS_WIN32)
00242   CloseHandle(hFile);
00243 #else
00244   /* TODO: Implement for non-Windowses */
00245   /* close(hFile); */
00246 #endif
00247   return true;
00248 }
00249 
00250 /** Breakpad-installed exception handler. This function gets called in the
00251  * event of a crash. If <b>showCrashReporter</b> is true, this will execute
00252  * the crash reporting application, passing it the name and location of the
00253  * generated minidump, the absolute path to the (now crashed) Vidalia
00254  * executable, and any arguments that may be needed to restart Vidalia.
00255  */
00256 bool
00257 minidump_callback(const _char_t *path,    // Path to the minidump file
00258                   const _char_t *id,      // Minidump UUID
00259                   void *context,          // Callback context
00260 #if defined(Q_OS_WIN32)
00261                   EXCEPTION_POINTERS *exInfo,
00262                   MDRawAssertionInfo *assertionInfo,
00263 #endif
00264                   bool succeeded)
00265 {
00266   if (! succeeded || ! showCrashReporter)
00267     return false;
00268 
00269   /* Write the extra dump info, such as application uptime, executable to
00270    * restart, and any necessary restart arguments. */
00271   write_extra_dump_info(path, id, time(NULL));
00272 
00273   /* Format the command line used to launch the crash reporter */
00274   _char_t commandLine[MAX_CMD_LEN] = TEXT("");
00275   append_string(commandLine, TEXT("\""), MAX_CMD_LEN);
00276   append_string(commandLine, crashReporterExecutable, MAX_CMD_LEN);
00277   append_string(commandLine, TEXT("\" \""), MAX_CMD_LEN);
00278   append_string(commandLine, path, MAX_CMD_LEN);
00279   append_string(commandLine, PATH_SEPARATOR, MAX_CMD_LEN);
00280   append_string(commandLine, id, MAX_CMD_LEN);
00281   size_t len = append_string(commandLine, TEXT(".dmp\""), MAX_CMD_LEN);
00282   if (len >= MAX_CMD_LEN)
00283     return false;
00284 
00285   /* Launch the crash reporter with the name and location of the minidump */
00286 #if defined(Q_OS_WIN32)
00287   PROCESS_INFORMATION pi;
00288   STARTUPINFOW si;
00289 
00290   ZeroMemory(&pi, sizeof(pi));
00291   ZeroMemory(&si, sizeof(si));
00292   si.cb = sizeof(si);
00293   si.dwFlags = STARTF_USESHOWWINDOW;
00294   si.wShowWindow = SW_SHOWDEFAULT;
00295 
00296   BOOL rc = CreateProcess(NULL, (LPWSTR)commandLine, NULL, NULL, FALSE, 0,
00297                           NULL, NULL, &si, &pi);
00298   if (rc) {
00299     CloseHandle(pi.hThread);
00300     CloseHandle(pi.hProcess);
00301   }
00302   TerminateProcess(GetCurrentProcess(), 1);
00303   return true;
00304 #else
00305   /* TODO: Implement for non-Windowses */
00306   return false;
00307 #endif
00308 }
00309 
00310 bool
00311 install_exception_handler(const QString &dumpPath)
00312 {
00313   /* Create a directory for the crash dumps if it doesn't already exist */
00314   QDir dumpDir(dumpPath);
00315   if (! dumpDir.exists() && ! dumpDir.mkdir("."))
00316     return false;
00317 
00318   /* Create the exception handler and specify where the dumps should go */
00319   exceptionHandler = new google_breakpad::ExceptionHandler(
00320 #if defined(Q_OS_WIN32)
00321                             dumpDir.absolutePath().toStdWString(),
00322 #else
00323                             dumpDir.absolutePath().toStdString(),
00324 #endif
00325                             NULL,
00326                             minidump_callback,
00327                             NULL,
00328 #if defined(Q_OS_WIN32)
00329                             google_breakpad::ExceptionHandler::HANDLER_ALL);
00330 #else
00331                             true);
00332 #endif
00333   if (! exceptionHandler)
00334     return false;
00335 
00336   startupTime = time(NULL);
00337   return true;
00338 }
00339 
00340 void
00341 remove_exception_handler(void)
00342 {
00343   if (exceptionHandler) {
00344     delete exceptionHandler;
00345     exceptionHandler = 0;
00346   }
00347 }
00348 
00349 bool
00350 set_crash_reporter(const QString &crashReporter)
00351 {
00352 #if defined(Q_OS_WIN32)
00353   if (crashReporter.length() <= CrashReporter::MAX_PATH_LEN) {
00354     crashReporter.toWCharArray(crashReporterExecutable);
00355     crashReporterExecutable[crashReporter.length()] = L'\0';
00356 #else
00357   QByteArray utf8 = crashReporter.toUtf8();
00358   if (utf8.length() <= CrashReporter::MAX_PATH_LEN) {
00359     memcpy(crashReporterExecutable, utf8.constData(), utf8.length());
00360     crashReporterExecutable[utf8.length()] = '\0';
00361 #endif
00362     showCrashReporter = true;
00363   } else {
00364     /* If the given path is longer than MAX_PATH_LEN, no crash reporting
00365      * application will be set since the user's platform wouldn't be able to
00366      * execute it anyway.
00367      */
00368     showCrashReporter = false;
00369   }
00370   return showCrashReporter;
00371 }
00372 
00373 bool
00374 set_restart_options(const QString &executable, const QStringList &arguments)
00375 {
00376   QByteArray exe = executable.toUtf8();
00377   if (exe.length() > MAX_CMD_LEN)
00378     return false;
00379 
00380   QByteArray args = string_format_arguments(arguments).toUtf8();
00381   if (args.length() > MAX_CMD_LEN)
00382     return false;
00383 
00384   memcpy(restartExecutable, exe.constData(), exe.length());
00385   restartExecutable[exe.length()] = '\0';
00386 
00387   memcpy(restartExecutableArgs, args.constData(), args.length());
00388   restartExecutableArgs[args.length()] = '\0';
00389 
00390   return true;
00391 }
00392 
00393 bool
00394 set_build_version(const QString &version)
00395 {
00396   if (version.length() > MAX_VERSION_LEN)
00397     return false;
00398 
00399   QByteArray ascii = version.toAscii();
00400   memcpy(buildVersion, ascii.constData(), ascii.length());
00401   buildVersion[ascii.length()] = '\0';
00402 
00403   return true;
00404 }
00405 
00406 }
00407