Vidalia  0.3.1
CrashReporter.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 /*
11 ** The append_string() function in this file is derived from the implementation
12 ** of strlcat() by Todd C. Miller. It is licensed as follows:
13 **
14 ** Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
15 ** All rights reserved.
16 **
17 ** Redistribution and use in source and binary forms, with or without
18 ** modification, are permitted provided that the following conditions
19 ** are met:
20 ** 1. Redistributions of source code must retain the above copyright
21 ** notice, this list of conditions and the following disclaimer.
22 ** 2. Redistributions in binary form must reproduce the above copyright
23 ** notice, this list of conditions and the following disclaimer in the
24 ** documentation and/or other materials provided with the distribution.
25 ** 3. The name of the author may not be used to endorse or promote products
26 ** derived from this software without specific prior written permission.
27 **
28 ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
29 ** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
30 ** AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
31 ** THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
34 ** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
35 ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
36 ** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 ** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 */
39 
40 /*
41 ** \file CrashReporter.h
42 ** \brief General routines to install a Breakpad-based exception handler and
43 ** set options related to launching the crash reporting application.
44 */
45 
46 #include "CrashReporter.h"
47 #include "stringutil.h"
48 
49 #if defined(Q_OS_WIN32)
50 #include <client/windows/handler/exception_handler.h>
51 #elif defined(Q_OS_MAC)
52 #include <client/mac/handler/exception_handler.h>
53 #include <sys/wait.h>
54 #include <fcntl.h>
55 #elif defined(Q_OS_LINUX)
56 #include <client/linux/handler/exception_handler.h>
57 #include <sys/wait.h>
58 #include <fcntl.h>
59 #elif defined(Q_OS_SOLARIS)
60 #include <client/solaris/handler/exception_handler.h>
61 #endif
62 
63 #include <QString>
64 #include <QStringList>
65 #include <QFileInfo>
66 #include <QDir>
67 
68 #include <time.h>
69 
70 
71 namespace CrashReporter
72 {
73 #if defined(Q_OS_WIN32)
74 typedef wchar_t _char_t;
75 typedef HANDLE _file_handle_t;
76 #define PATH_SEPARATOR TEXT("\\")
77 # ifdef _USE_32BIT_TIME_T
78 # define TIME_TO_STRING(buf, buflen, t) \
79  _ltoa_s(t, buf, buflen, 10)
80 # else
81 # define TIME_TO_STRING(buf, buflen, t) \
82  _i64toa_s(t, buf, buflen, 10)
83 # endif
84 #else
85 typedef char _char_t;
86 typedef int _file_handle_t;
87 #define PATH_SEPARATOR "/"
88 #define TEXT(x) (x)
89 #define TIME_TO_STRING(buf, buflen, t) \
90  snprintf(buf, buflen, "%ld", t)
91 #endif
92 
93 /** Pointer to the Breakpad-installed exception handler called if Vidalia
94  * crashes.
95  * \sa install_exception_handler()
96  */
97 static google_breakpad::ExceptionHandler *exceptionHandler = 0;
98 
99 /** If true, the crash reporting application will be displayed when the
100  * Breakpad-installed exception handler is called. Otherwise, the system
101  * will handle the exception itself.
102  * \sa set_crash_reporter()
103  */
104 static bool showCrashReporter = false;
105 
106 /** Absolute path of the crash reporting application that will be launched
107  * from the exception handler.
108  * \sa set_crash_reporter()
109  */
111 
112 /** Version information for the application being monitored for crashes.
113  * The version will be written to the extra information file alongside
114  * the minidump.
115  */
116 static char buildVersion[MAX_VERSION_LEN + 1] = "";
117 
118 /** Path and filename of the application to restart after displaying
119  * the crash reporting dialog. The contents of this string are encoded
120  * in UTF-8.
121  * \sa set_restart_options()
122  */
123 static char restartExecutable[MAX_CMD_LEN + 1] = "";
124 
125 /** Additional arguments to use when restarting the crashed application.
126  * The contents of this string are encoded in UTF-8.
127  * \sa set_restart_options()
128  */
129 static char restartExecutableArgs[MAX_CMD_LEN + 1] = "";
130 
131 
132 /** Records the time at which install_exception_handler() is called, which
133  * is usually as early as possible during application startup. This is used
134  * in minidump_callback() to determine how long the application was running
135  * before it crashed.
136  * \sa install_exception_handler()
137  * \sa minidump_callback()
138  */
139 static time_t startupTime = 0;
140 
141 
142 /** Slightly modified version of the strlcat() implementation by Todd C.
143  * Miller (see the top of this file or the LICENSE file for license details),
144  * that supports arguments of either wchar_t* on Windows or the usual char*
145  * everywhere else but retains the semantics of strlcat().
146  */
147 static size_t
148 append_string(_char_t *dst, const _char_t *src, size_t siz)
149 {
150  _char_t *d = dst;
151  const _char_t *s = src;
152  size_t n = siz;
153  size_t dlen;
154 
155  /* Find the end of dst and adjust bytes left but don't go past end */
156  while (n-- != 0 && *d != TEXT('\0'))
157  d++;
158  dlen = d - dst;
159  n = siz - dlen;
160 
161  if (n == 0)
162 #if defined(Q_OS_WIN32)
163  return (dlen + wcslen(s));
164 #else
165  return(dlen + strlen(s));
166 #endif
167 
168  while (*s != TEXT('\0')) {
169  if (n != 1) {
170  *d++ = *s;
171  n--;
172  }
173  s++;
174  }
175  *d = TEXT('\0');
176 
177  return(dlen + (s - src)); /* count does not include NUL */
178 }
179 
180 /** Writes the formatted string "<b>key</b>=</b>val\n" to the file handle
181  * specified by <b>hFile</b>. On Windows, <b>hFile</b> is a HANDLE. Everywhere
182  * else, <b>hFile</b> is an int.
183  */
184 static void
185 write_keyval_to_file(_file_handle_t hFile, const char *key, const char *val)
186 {
187 #if defined(Q_OS_WIN32)
188  DWORD dwWritten;
189  WriteFile(hFile, key, strlen(key), &dwWritten, NULL);
190  WriteFile(hFile, "=", 1, &dwWritten, NULL);
191  WriteFile(hFile, val, strlen(val), &dwWritten, NULL);
192  WriteFile(hFile, "\n", 1, &dwWritten, NULL);
193 #else
194  write(hFile, key, strlen(key));
195  write(hFile, "=", 1);
196  write(hFile, val, strlen(val));
197  write(hFile, "\n", 1);
198 #endif
199 }
200 
201 /** Writes to a file extra information used by the crash reporting
202  * application such as how long the application was running before it
203  * crashed, the application to restart, as well as any extra arguments.
204  * The contents of the file are formatted as a series of "Key=Val\n" pairs.
205  * The written file has the same path and base filename as the minidump
206  * file, with ".info" appended to the end. Returns true if the file was
207  * created succesfully. Otherwise, returns false.
208  */
209 static bool
210 write_extra_dump_info(const _char_t *path, const _char_t *id, time_t crashTime)
211 {
212  static const char *KeyBuildVersion = "BuildVersion";
213  static const char *KeyCrashTime = "CrashTime";
214  static const char *KeyStartupTime = "StartupTime";
215  static const char *KeyRestartExecutable = "RestartExecutable";
216  static const char *KeyRestartExecutableArgs = "RestartExecutableArgs";
217 
218  _char_t extraInfoPath[MAX_PATH_LEN] = TEXT("");
219  append_string(extraInfoPath, path, MAX_PATH_LEN);
220  append_string(extraInfoPath, PATH_SEPARATOR, MAX_PATH_LEN);
221  append_string(extraInfoPath, id, MAX_PATH_LEN);
222  size_t len = append_string(extraInfoPath, TEXT(".dmp.info"), MAX_PATH_LEN);
223  if (len >= MAX_PATH_LEN)
224  return false;
225 
226 #if defined(Q_OS_WIN32)
227  HANDLE hFile = CreateFile(extraInfoPath, GENERIC_WRITE, 0, NULL,
228  CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
229  if (hFile == INVALID_HANDLE_VALUE)
230  return false;
231 #else
232  _file_handle_t hFile = creat(extraInfoPath, S_IRUSR | S_IWUSR);
233  if(hFile == -1) {
234  return false;
235  }
236 #endif
237 
238  char crashTimeString[24], startupTimeString[24];
239  TIME_TO_STRING(crashTimeString, 24, crashTime);
240  TIME_TO_STRING(startupTimeString, 24, startupTime);
241 
242  write_keyval_to_file(hFile, KeyBuildVersion, buildVersion);
243  write_keyval_to_file(hFile, KeyCrashTime, crashTimeString);
244  write_keyval_to_file(hFile, KeyStartupTime, startupTimeString);
245  write_keyval_to_file(hFile, KeyRestartExecutable, restartExecutable);
246  write_keyval_to_file(hFile, KeyRestartExecutableArgs, restartExecutableArgs);
247 
248 #if defined(Q_OS_WIN32)
249  CloseHandle(hFile);
250 #else
251  close(hFile);
252 #endif
253  return true;
254 }
255 
256 /** Breakpad-installed exception handler. This function gets called in the
257  * event of a crash. If <b>showCrashReporter</b> is true, this will execute
258  * the crash reporting application, passing it the name and location of the
259  * generated minidump, the absolute path to the (now crashed) Vidalia
260  * executable, and any arguments that may be needed to restart Vidalia.
261  */
262 bool
263 minidump_callback(const _char_t *path, // Path to the minidump file
264  const _char_t *id, // Minidump UUID
265  void *context, // Callback context
266 #if defined(Q_OS_WIN32)
267  EXCEPTION_POINTERS *exInfo,
268  MDRawAssertionInfo *assertionInfo,
269 #endif
270  bool succeeded)
271 {
272  if (! succeeded || ! showCrashReporter)
273  return false;
274 
275  /* Write the extra dump info, such as application uptime, executable to
276  * restart, and any necessary restart arguments. */
277  write_extra_dump_info(path, id, time(NULL));
278 
279 #if defined(Q_OS_WIN32)
280  /* Format the command line used to launch the crash reporter */
281  _char_t commandLine[MAX_CMD_LEN] = TEXT("");
282  append_string(commandLine, TEXT("\""), MAX_CMD_LEN);
284  append_string(commandLine, TEXT("\" \""), MAX_CMD_LEN);
285  append_string(commandLine, path, MAX_CMD_LEN);
287  append_string(commandLine, id, MAX_CMD_LEN);
288  size_t len = append_string(commandLine, TEXT(".dmp\""), MAX_CMD_LEN);
289  if (len >= MAX_CMD_LEN)
290  return false;
291 
292  /* Launch the crash reporter with the name and location of the minidump */
293  PROCESS_INFORMATION pi;
294  STARTUPINFOW si;
295 
296  ZeroMemory(&pi, sizeof(pi));
297  ZeroMemory(&si, sizeof(si));
298  si.cb = sizeof(si);
299  si.dwFlags = STARTF_USESHOWWINDOW;
300  si.wShowWindow = SW_SHOWDEFAULT;
301 
302  BOOL rc = CreateProcess(NULL, (LPWSTR)commandLine, NULL, NULL, FALSE, 0,
303  NULL, NULL, &si, &pi);
304  if (rc) {
305  CloseHandle(pi.hThread);
306  CloseHandle(pi.hProcess);
307  }
308  TerminateProcess(GetCurrentProcess(), 1);
309  return true;
310 #else
311  /* Format the command line used to launch the crash reporter */
312  _char_t args[MAX_CMD_LEN] = TEXT("");
313  size_t len;
314 
315  append_string(args, path, MAX_CMD_LEN);
317  append_string(args, id, MAX_CMD_LEN);
318  len = append_string(args, TEXT(".dmp"), MAX_CMD_LEN);
319  if (len >= MAX_CMD_LEN)
320  return false;
321 
322  char *nargs[] = {crashReporterExecutable, args, NULL};
323 
324  pid_t p = fork(), ret;
325  int status;
326  if(p == 0) {
327  execv(crashReporterExecutable, nargs);
328  } else {
329  ret = wait(&status);
330  if(ret == -1)
331  return false;
332  }
333  return true;
334 #endif
335 }
336 
337 bool
338 install_exception_handler(const QString &dumpPath)
339 {
340  /* Create a directory for the crash dumps if it doesn't already exist */
341  QDir dumpDir(dumpPath);
342  if (! dumpDir.exists() && ! dumpDir.mkdir("."))
343  return false;
344 
345  /* Create the exception handler and specify where the dumps should go */
346  exceptionHandler = new google_breakpad::ExceptionHandler(
347 #if defined(Q_OS_WIN32)
348  dumpDir.absolutePath().toStdWString(),
349 #else
350  dumpDir.absolutePath().toStdString(),
351 #endif
352  NULL,
354  NULL,
355 #if defined(Q_OS_WIN32)
356  google_breakpad::ExceptionHandler::HANDLER_ALL);
357 #else
358  true);
359 #endif
360  if (! exceptionHandler)
361  return false;
362 
363  startupTime = time(NULL);
364  return true;
365 }
366 
367 void
369 {
370  if (exceptionHandler) {
371  delete exceptionHandler;
372  exceptionHandler = 0;
373  }
374 }
375 
376 bool
377 set_crash_reporter(const QString &crashReporter)
378 {
379 #if defined(Q_OS_WIN32)
380  if (crashReporter.length() <= CrashReporter::MAX_PATH_LEN) {
381  crashReporter.toWCharArray(crashReporterExecutable);
382  crashReporterExecutable[crashReporter.length()] = L'\0';
383 #else
384  QByteArray utf8 = crashReporter.toUtf8();
385  if (utf8.length() <= CrashReporter::MAX_PATH_LEN) {
386  memcpy(crashReporterExecutable, utf8.constData(), utf8.length());
387  crashReporterExecutable[utf8.length()] = '\0';
388 #endif
389  showCrashReporter = true;
390  } else {
391  /* If the given path is longer than MAX_PATH_LEN, no crash reporting
392  * application will be set since the user's platform wouldn't be able to
393  * execute it anyway.
394  */
395  showCrashReporter = false;
396  }
397  return showCrashReporter;
398 }
399 
400 bool
401 set_restart_options(const QString &executable, const QStringList &arguments)
402 {
403  QByteArray exe = executable.toUtf8();
404  if (exe.length() > MAX_CMD_LEN)
405  return false;
406 
407  QByteArray args = string_format_arguments(arguments).toUtf8();
408  if (args.length() > MAX_CMD_LEN)
409  return false;
410 
411  memcpy(restartExecutable, exe.constData(), exe.length());
412  restartExecutable[exe.length()] = '\0';
413 
414  memcpy(restartExecutableArgs, args.constData(), args.length());
415  restartExecutableArgs[args.length()] = '\0';
416 
417  return true;
418 }
419 
420 bool
421 set_build_version(const QString &version)
422 {
423  if (version.length() > MAX_VERSION_LEN)
424  return false;
425 
426  QByteArray ascii = version.toAscii();
427  memcpy(buildVersion, ascii.constData(), ascii.length());
428  buildVersion[ascii.length()] = '\0';
429 
430  return true;
431 }
432 
433 }
434