Vidalia  0.2.17
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.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the 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 #include <sys/wait.h>
00054 #include <fcntl.h>
00055 #elif defined(Q_OS_LINUX)
00056 #include <client/linux/handler/exception_handler.h>
00057 #include <sys/wait.h>
00058 #include <fcntl.h>
00059 #elif defined(Q_OS_SOLARIS)
00060 #include <client/solaris/handler/exception_handler.h>
00061 #endif
00062 
00063 #include <QString>
00064 #include <QStringList>
00065 #include <QFileInfo>
00066 #include <QDir>
00067 
00068 #include <time.h>
00069 
00070 
00071 namespace CrashReporter
00072 {
00073 #if defined(Q_OS_WIN32)
00074 typedef wchar_t         _char_t;
00075 typedef HANDLE          _file_handle_t;
00076 #define PATH_SEPARATOR  TEXT("\\")
00077 # ifdef _USE_32BIT_TIME_T
00078 #   define TIME_TO_STRING(buf, buflen, t) \
00079       _ltoa_s(t, buf, buflen, 10)
00080 # else
00081 #   define TIME_TO_STRING(buf, buflen, t) \
00082       _i64toa_s(t, buf, buflen, 10)
00083 # endif
00084 #else
00085 typedef char            _char_t;
00086 typedef int             _file_handle_t;
00087 #define PATH_SEPARATOR  "/"
00088 #define TEXT(x)         (x)
00089 #define TIME_TO_STRING(buf, buflen, t) \
00090   snprintf(buf, buflen, "%ld", t)
00091 #endif
00092 
00093 /** Pointer to the Breakpad-installed exception handler called if Vidalia
00094  * crashes.
00095  * \sa install_exception_handler()
00096  */
00097 static google_breakpad::ExceptionHandler *exceptionHandler = 0;
00098 
00099 /** If true, the crash reporting application will be displayed when the
00100  * Breakpad-installed exception handler is called. Otherwise, the system
00101  * will handle the exception itself.
00102  * \sa set_crash_reporter()
00103  */
00104 static bool showCrashReporter = false;
00105 
00106 /** Absolute path of the crash reporting application that will be launched
00107  * from the exception handler.
00108  * \sa set_crash_reporter()
00109  */
00110 static _char_t crashReporterExecutable[MAX_PATH_LEN + 1] = TEXT("");
00111 
00112 /** Version information for the application being monitored for crashes.
00113  * The version will be written to the extra information file alongside
00114  * the minidump.
00115  */
00116 static char buildVersion[MAX_VERSION_LEN + 1] = "";
00117 
00118 /** Path and filename of the application to restart after displaying
00119  * the crash reporting dialog. The contents of this string are encoded
00120  * in UTF-8.
00121  * \sa set_restart_options()
00122  */
00123 static char restartExecutable[MAX_CMD_LEN + 1] = "";
00124 
00125 /** Additional arguments to use when restarting the crashed application.
00126  * The contents of this string are encoded in UTF-8.
00127  * \sa set_restart_options()
00128  */
00129 static char restartExecutableArgs[MAX_CMD_LEN + 1] = "";
00130 
00131 
00132 /** Records the time at which install_exception_handler() is called, which
00133  * is usually as early as possible during application startup. This is used
00134  * in minidump_callback() to determine how long the application was running
00135  * before it crashed.
00136  * \sa install_exception_handler()
00137  * \sa minidump_callback()
00138  */
00139 static time_t startupTime = 0;
00140 
00141 
00142 /** Slightly modified version of the strlcat() implementation by Todd C. 
00143  * Miller (see the top of this file or the LICENSE file for license details),
00144  * that supports arguments of either wchar_t* on Windows or the usual char*
00145  * everywhere else but retains the semantics of strlcat().
00146  */
00147 static size_t
00148 append_string(_char_t *dst, const _char_t *src, size_t siz)
00149 {
00150   _char_t *d = dst;
00151   const _char_t *s = src;
00152   size_t n = siz;
00153   size_t dlen;
00154 
00155   /* Find the end of dst and adjust bytes left but don't go past end */
00156   while (n-- != 0 && *d != TEXT('\0'))
00157     d++;
00158   dlen = d - dst;
00159   n = siz - dlen;
00160 
00161   if (n == 0)
00162 #if defined(Q_OS_WIN32)
00163     return (dlen + wcslen(s));
00164 #else
00165     return(dlen + strlen(s));
00166 #endif
00167 
00168   while (*s != TEXT('\0')) {
00169     if (n != 1) {
00170       *d++ = *s;
00171       n--;
00172     }
00173     s++;
00174   }
00175   *d = TEXT('\0');
00176 
00177   return(dlen + (s - src));     /* count does not include NUL */
00178 }
00179 
00180 /** Writes the formatted string "<b>key</b>=</b>val\n" to the file handle
00181  * specified by <b>hFile</b>. On Windows, <b>hFile</b> is a HANDLE. Everywhere
00182  * else, <b>hFile</b> is an int.
00183  */
00184 static void
00185 write_keyval_to_file(_file_handle_t hFile, const char *key, const char *val)
00186 {
00187 #if defined(Q_OS_WIN32)
00188   DWORD dwWritten;
00189   WriteFile(hFile, key, strlen(key), &dwWritten, NULL);
00190   WriteFile(hFile, "=", 1, &dwWritten, NULL);
00191   WriteFile(hFile, val, strlen(val), &dwWritten, NULL);
00192   WriteFile(hFile, "\n", 1, &dwWritten, NULL);
00193 #else
00194   write(hFile, key, strlen(key));
00195   write(hFile, "=", 1);
00196   write(hFile, val, strlen(val));
00197   write(hFile, "\n", 1);
00198 #endif
00199 }
00200 
00201 /** Writes to a file extra information used by the crash reporting
00202  * application such as how long the application was running before it
00203  * crashed, the application to restart, as well as any extra arguments.
00204  * The contents of the file are formatted as a series of "Key=Val\n" pairs.
00205  * The written file has the same path and base filename as the minidump
00206  * file, with ".info" appended to the end. Returns true if the file was
00207  * created succesfully. Otherwise, returns false.
00208  */
00209 static bool
00210 write_extra_dump_info(const _char_t *path, const _char_t *id, time_t crashTime)
00211 {
00212   static const char *KeyBuildVersion = "BuildVersion";
00213   static const char *KeyCrashTime = "CrashTime";
00214   static const char *KeyStartupTime = "StartupTime";
00215   static const char *KeyRestartExecutable = "RestartExecutable";
00216   static const char *KeyRestartExecutableArgs = "RestartExecutableArgs";
00217 
00218   _char_t extraInfoPath[MAX_PATH_LEN] = TEXT("");
00219   append_string(extraInfoPath, path, MAX_PATH_LEN);
00220   append_string(extraInfoPath, PATH_SEPARATOR, MAX_PATH_LEN);
00221   append_string(extraInfoPath, id, MAX_PATH_LEN);
00222   size_t len = append_string(extraInfoPath, TEXT(".dmp.info"), MAX_PATH_LEN);
00223   if (len >= MAX_PATH_LEN)
00224     return false;
00225 
00226 #if defined(Q_OS_WIN32)
00227   HANDLE hFile = CreateFile(extraInfoPath, GENERIC_WRITE, 0, NULL,
00228                             CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
00229   if (hFile == INVALID_HANDLE_VALUE)
00230     return false;
00231 #else
00232   _file_handle_t hFile = creat(extraInfoPath, S_IRUSR | S_IWUSR);
00233   if(hFile == -1) {
00234     return false;
00235   }
00236 #endif
00237   
00238   char crashTimeString[24], startupTimeString[24];
00239   TIME_TO_STRING(crashTimeString, 24, crashTime);
00240   TIME_TO_STRING(startupTimeString, 24, startupTime);
00241 
00242   write_keyval_to_file(hFile, KeyBuildVersion, buildVersion);
00243   write_keyval_to_file(hFile, KeyCrashTime, crashTimeString);
00244   write_keyval_to_file(hFile, KeyStartupTime, startupTimeString);
00245   write_keyval_to_file(hFile, KeyRestartExecutable, restartExecutable);
00246   write_keyval_to_file(hFile, KeyRestartExecutableArgs, restartExecutableArgs);
00247 
00248 #if defined(Q_OS_WIN32)
00249   CloseHandle(hFile);
00250 #else
00251   close(hFile);
00252 #endif
00253   return true;
00254 }
00255 
00256 /** Breakpad-installed exception handler. This function gets called in the
00257  * event of a crash. If <b>showCrashReporter</b> is true, this will execute
00258  * the crash reporting application, passing it the name and location of the
00259  * generated minidump, the absolute path to the (now crashed) Vidalia
00260  * executable, and any arguments that may be needed to restart Vidalia.
00261  */
00262 bool
00263 minidump_callback(const _char_t *path,    // Path to the minidump file
00264                   const _char_t *id,      // Minidump UUID
00265                   void *context,          // Callback context
00266 #if defined(Q_OS_WIN32)
00267                   EXCEPTION_POINTERS *exInfo,
00268                   MDRawAssertionInfo *assertionInfo,
00269 #endif
00270                   bool succeeded)
00271 {
00272   if (! succeeded || ! showCrashReporter)
00273     return false;
00274 
00275   /* Write the extra dump info, such as application uptime, executable to
00276    * restart, and any necessary restart arguments. */
00277   write_extra_dump_info(path, id, time(NULL));
00278 
00279 #if defined(Q_OS_WIN32)
00280   /* Format the command line used to launch the crash reporter */
00281   _char_t commandLine[MAX_CMD_LEN] = TEXT("");
00282   append_string(commandLine, TEXT("\""), MAX_CMD_LEN);
00283   append_string(commandLine, crashReporterExecutable, MAX_CMD_LEN);
00284   append_string(commandLine, TEXT("\" \""), MAX_CMD_LEN);
00285   append_string(commandLine, path, MAX_CMD_LEN);
00286   append_string(commandLine, PATH_SEPARATOR, MAX_CMD_LEN);
00287   append_string(commandLine, id, MAX_CMD_LEN);
00288   size_t len = append_string(commandLine, TEXT(".dmp\""), MAX_CMD_LEN);
00289   if (len >= MAX_CMD_LEN)
00290     return false;
00291 
00292   /* Launch the crash reporter with the name and location of the minidump */
00293   PROCESS_INFORMATION pi;
00294   STARTUPINFOW si;
00295 
00296   ZeroMemory(&pi, sizeof(pi));
00297   ZeroMemory(&si, sizeof(si));
00298   si.cb = sizeof(si);
00299   si.dwFlags = STARTF_USESHOWWINDOW;
00300   si.wShowWindow = SW_SHOWDEFAULT;
00301 
00302   BOOL rc = CreateProcess(NULL, (LPWSTR)commandLine, NULL, NULL, FALSE, 0,
00303                           NULL, NULL, &si, &pi);
00304   if (rc) {
00305     CloseHandle(pi.hThread);
00306     CloseHandle(pi.hProcess);
00307   }
00308   TerminateProcess(GetCurrentProcess(), 1);
00309   return true;
00310 #else
00311   /* Format the command line used to launch the crash reporter */
00312   _char_t args[MAX_CMD_LEN] = TEXT("");
00313   size_t len;
00314 
00315   append_string(args, path, MAX_CMD_LEN);
00316   append_string(args, PATH_SEPARATOR, MAX_CMD_LEN);
00317   append_string(args, id, MAX_CMD_LEN);
00318   len = append_string(args, TEXT(".dmp"), MAX_CMD_LEN);
00319   if (len >= MAX_CMD_LEN)
00320     return false;
00321 
00322   char *nargs[] = {crashReporterExecutable, args, NULL};
00323 
00324   pid_t p = fork(), ret;
00325   int status;
00326   if(p == 0) {
00327     execv(crashReporterExecutable, nargs);
00328   } else {
00329     ret = wait(&status);
00330     if(ret == -1)
00331       return false;
00332   }
00333   return true;
00334 #endif
00335 }
00336 
00337 bool
00338 install_exception_handler(const QString &dumpPath)
00339 {
00340   /* Create a directory for the crash dumps if it doesn't already exist */
00341   QDir dumpDir(dumpPath);
00342   if (! dumpDir.exists() && ! dumpDir.mkdir("."))
00343     return false;
00344 
00345   /* Create the exception handler and specify where the dumps should go */
00346   exceptionHandler = new google_breakpad::ExceptionHandler(
00347 #if defined(Q_OS_WIN32)
00348                             dumpDir.absolutePath().toStdWString(),
00349 #else
00350                             dumpDir.absolutePath().toStdString(),
00351 #endif
00352                             NULL,
00353                             minidump_callback,
00354                             NULL,
00355 #if defined(Q_OS_WIN32)
00356                             google_breakpad::ExceptionHandler::HANDLER_ALL);
00357 #else
00358                             true);
00359 #endif
00360   if (! exceptionHandler)
00361     return false;
00362 
00363   startupTime = time(NULL);
00364   return true;
00365 }
00366 
00367 void
00368 remove_exception_handler(void)
00369 {
00370   if (exceptionHandler) {
00371     delete exceptionHandler;
00372     exceptionHandler = 0;
00373   }
00374 }
00375 
00376 bool
00377 set_crash_reporter(const QString &crashReporter)
00378 {
00379 #if defined(Q_OS_WIN32)
00380   if (crashReporter.length() <= CrashReporter::MAX_PATH_LEN) {
00381     crashReporter.toWCharArray(crashReporterExecutable);
00382     crashReporterExecutable[crashReporter.length()] = L'\0';
00383 #else
00384   QByteArray utf8 = crashReporter.toUtf8();
00385   if (utf8.length() <= CrashReporter::MAX_PATH_LEN) {
00386     memcpy(crashReporterExecutable, utf8.constData(), utf8.length());
00387     crashReporterExecutable[utf8.length()] = '\0';
00388 #endif
00389     showCrashReporter = true;
00390   } else {
00391     /* If the given path is longer than MAX_PATH_LEN, no crash reporting
00392      * application will be set since the user's platform wouldn't be able to
00393      * execute it anyway.
00394      */
00395     showCrashReporter = false;
00396   }
00397   return showCrashReporter;
00398 }
00399 
00400 bool
00401 set_restart_options(const QString &executable, const QStringList &arguments)
00402 {
00403   QByteArray exe = executable.toUtf8();
00404   if (exe.length() > MAX_CMD_LEN)
00405     return false;
00406 
00407   QByteArray args = string_format_arguments(arguments).toUtf8();
00408   if (args.length() > MAX_CMD_LEN)
00409     return false;
00410 
00411   memcpy(restartExecutable, exe.constData(), exe.length());
00412   restartExecutable[exe.length()] = '\0';
00413 
00414   memcpy(restartExecutableArgs, args.constData(), args.length());
00415   restartExecutableArgs[args.length()] = '\0';
00416 
00417   return true;
00418 }
00419 
00420 bool
00421 set_build_version(const QString &version)
00422 {
00423   if (version.length() > MAX_VERSION_LEN)
00424     return false;
00425 
00426   QByteArray ascii = version.toAscii();
00427   memcpy(buildVersion, ascii.constData(), ascii.length());
00428   buildVersion[ascii.length()] = '\0';
00429 
00430   return true;
00431 }
00432 
00433 }
00434