Vidalia 0.2.12
|
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