e19d86090826a2dd3b4d014e3b2c5ea81d6adccc
[claws.git] / src / crash.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002 by the Sylpheed Claws Team and Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #       include <config.h>
22 #endif
23
24 #include <glib.h>
25 #include <gtk/gtk.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <signal.h>
29
30 #include <errno.h>
31 #include <fcntl.h>
32
33 #if HAVE_SYS_UTSNAME_H
34 #       include <sys/utsname.h>
35 #endif
36
37 #if defined(__GNU_LIBRARY__)
38 #       include <gnu/libc-version.h>
39 #endif
40
41 #include "intl.h"
42 #include "crash.h"
43 #include "utils.h"
44 #include "prefs.h"
45 #include "version.h"
46
47 #if 0
48 #include "gtkutils.h"
49 #include "pixmaps/notice_error.xpm"
50 #endif
51
52 /*
53  * NOTE 1: the crash dialog is called when sylpheed is not 
54  * initialized, so do not assume settings are available.
55  * for example, loading / creating pixmaps seems not 
56  * to be possible.
57  */
58
59 static void crash_handler                       (int sig);
60 static gboolean is_crash_dialog_allowed         (void);
61 static void crash_debug                         (unsigned long crash_pid, GString *string);
62 static gboolean crash_create_debugger_file      (void);
63
64 static const gchar *get_compiled_in_features    (void);
65 static const gchar *get_lib_version             (void);
66 static const gchar *get_operating_system        (void);
67
68 /***/
69
70 static const gchar *DEBUG_SCRIPT = "bt full\nq";
71
72 /***/
73
74 /*
75  *\brief        (can't get pixmap working, so discarding it)
76  */
77 static GtkWidget *crash_dialog_new(const gchar *text, const gchar *debug_output)
78 {
79         GtkWidget *window1;
80         GtkWidget *vbox1;
81         GtkWidget *hbox1;
82         GtkWidget *label1;
83         GtkWidget *frame1;
84         GtkWidget *scrolledwindow1;
85         GtkWidget *text1;
86         GtkWidget *hbuttonbox3;
87         GtkWidget *hbuttonbox4;
88         GtkWidget *button3;
89         GtkWidget *button4;
90         GtkWidget *button5;
91         GtkWidget *pixwid;
92         GdkPixmap *pix;
93         GdkBitmap *msk;
94
95         window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL);
96         gtk_container_set_border_width(GTK_CONTAINER(window1), 5);
97         gtk_window_set_title(GTK_WINDOW(window1), _("Sylpheed has crashed"));
98         gtk_window_set_position(GTK_WINDOW(window1), GTK_WIN_POS_CENTER);
99         gtk_window_set_modal(GTK_WINDOW(window1), TRUE);
100         gtk_window_set_default_size(GTK_WINDOW(window1), 460, 272);
101
102
103         vbox1 = gtk_vbox_new(FALSE, 2);
104         gtk_widget_show(vbox1);
105         gtk_container_add(GTK_CONTAINER(window1), vbox1);
106
107         hbox1 = gtk_hbox_new(FALSE, 4);
108         gtk_widget_show(hbox1);
109         gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, TRUE, 0);
110         gtk_container_set_border_width(GTK_CONTAINER(hbox1), 4);
111
112 #if 0
113         PIXMAP_CREATE(window1, pix, msk, notice_error_xpm);
114         pixwid = gtk_pixmap_new(pix, msk);
115         gtk_widget_show(pixwid);
116         gtk_box_pack_start(GTK_BOX(hbox1), pixwid, TRUE, TRUE, 0);
117 #endif  
118
119         label1 = gtk_label_new
120             (g_strdup_printf(_("%s.\n Please file a bug report and include the information below."), text));
121         gtk_widget_show(label1);
122         gtk_box_pack_start(GTK_BOX(hbox1), label1, TRUE, TRUE, 0);
123         gtk_misc_set_alignment(GTK_MISC(label1), 7.45058e-09, 0.5);
124
125         frame1 = gtk_frame_new(_("Debug log"));
126         gtk_widget_show(frame1);
127         gtk_box_pack_start(GTK_BOX(vbox1), frame1, TRUE, TRUE, 0);
128
129         scrolledwindow1 = gtk_scrolled_window_new(NULL, NULL);
130         gtk_widget_show(scrolledwindow1);
131         gtk_container_add(GTK_CONTAINER(frame1), scrolledwindow1);
132         gtk_container_set_border_width(GTK_CONTAINER(scrolledwindow1), 3);
133         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow1),
134                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
135
136         text1 = gtk_text_new(NULL, NULL);
137         gtk_widget_show(text1);
138         gtk_container_add(GTK_CONTAINER(scrolledwindow1), text1);
139
140         gtk_text_insert(GTK_TEXT(text1), NULL, NULL, NULL,
141                         g_strdup_printf(
142                                 _("Sylpheed version %s\nGTK+ version %d.%d.%d\nFeatures: %s\nOperating system: %s\nC Library: %s\n--\n%s"),
143                                 VERSION,
144                                 gtk_major_version, gtk_minor_version, gtk_micro_version,
145                                 get_compiled_in_features(),
146                                 get_operating_system(),
147                                 get_lib_version(),
148                                 debug_output),
149                         -1);
150
151         hbuttonbox3 = gtk_hbutton_box_new();
152         gtk_widget_show(hbuttonbox3);
153         gtk_box_pack_start(GTK_BOX(vbox1), hbuttonbox3, FALSE, FALSE, 0);
154
155         hbuttonbox4 = gtk_hbutton_box_new();
156         gtk_widget_show(hbuttonbox4);
157         gtk_box_pack_start(GTK_BOX(vbox1), hbuttonbox4, FALSE, FALSE, 0);
158
159         button3 = gtk_button_new_with_label(_("Close"));
160         gtk_widget_show(button3);
161         gtk_container_add(GTK_CONTAINER(hbuttonbox4), button3);
162         GTK_WIDGET_SET_FLAGS(button3, GTK_CAN_DEFAULT);
163
164         button4 = gtk_button_new_with_label(_("Save..."));
165         gtk_widget_show(button4);
166         gtk_container_add(GTK_CONTAINER(hbuttonbox4), button4);
167         GTK_WIDGET_SET_FLAGS(button4, GTK_CAN_DEFAULT);
168
169         button5 = gtk_button_new_with_label(_("Create bug report"));
170         gtk_widget_show(button5);
171         gtk_container_add(GTK_CONTAINER(hbuttonbox4), button5);
172         GTK_WIDGET_SET_FLAGS(button5, GTK_CAN_DEFAULT);
173         
174         gtk_signal_connect(GTK_OBJECT(window1), "delete_event",
175                            GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
176         gtk_signal_connect(GTK_OBJECT(button3),   "clicked",
177                            GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
178
179         gtk_widget_show(window1);
180
181         gtk_main();
182         return window1;
183 }
184
185 /***/
186
187 /*
188  *\brief        install crash handlers
189  */
190 void crash_install_handlers(void)
191 {
192 #if HAVE_GDB
193         sigset_t mask;
194
195         if (!is_crash_dialog_allowed()) return;
196
197         sigemptyset(&mask);
198
199 #ifdef SIGSEGV
200         signal(SIGSEGV, crash_handler);
201         sigaddset(&mask, SIGSEGV);
202 #endif
203         
204 #ifdef SIGFPE
205         signal(SIGFPE, crash_handler);
206         sigaddset(&mask, SIGFPE);
207 #endif
208
209 #ifdef SIGILL
210         signal(SIGILL, crash_handler);
211         sigaddset(&mask, SIGILL);
212 #endif
213
214 #ifdef SIGABRT
215         signal(SIGABRT, crash_handler);
216         sigaddset(&mask, SIGABRT);
217 #endif
218
219         sigprocmask(SIG_UNBLOCK, &mask, 0);
220 #endif /* HAVE_GDB */   
221 }
222
223 /***/
224
225 /*
226  *\brief        crash dialog entry point 
227  */
228 void crash_main(const char *arg) 
229 {
230 #if HAVE_GDB
231         gchar *text;
232         gchar **tokens;
233         unsigned long pid;
234         GString *output;
235
236         crash_create_debugger_file();
237         tokens = g_strsplit(arg, ",", 0);
238
239         pid = atol(tokens[0]);
240         text = g_strdup_printf(_("Sylpheed process (%ld) received signal %ld"),
241                                pid, atol(tokens[1]));
242
243         output = g_string_new("");     
244         crash_debug(pid, output);
245         crash_dialog_new(text, output->str);
246         g_string_free(output, TRUE);
247         g_free(text);
248         g_strfreev(tokens);
249 #endif /* HAVE_GDB */   
250 }
251
252 /*
253  *\brief        create debugger script file in sylpheed directory.
254  *              all the other options (creating temp files) looked too 
255  *              convoluted.
256  */
257 static gboolean crash_create_debugger_file(void)
258 {
259         PrefFile *pf;
260         gchar *filespec = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, DEBUGGERRC, NULL);
261
262         pf = prefs_write_open(filespec);
263         g_free(filespec);
264         if (pf) 
265                 fprintf(pf->fp, DEBUG_SCRIPT);
266         prefs_write_close(pf);  
267 }
268
269 /*
270  *\brief        launches debugger and attaches it to crashed sylpheed
271  */
272 static void crash_debug(unsigned long crash_pid, GString *string)
273 {
274         int choutput[2];
275         pid_t pid;
276
277         pipe(choutput);
278
279         if (0 == (pid = fork())) {
280                 char *argp[9];
281                 char **argptr = argp;
282                 gchar *filespec = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, DEBUGGERRC, NULL);
283
284                 setgid(getgid());
285                 setuid(getuid());
286
287                 /*
288                  * setup debugger to attach to crashed sylpheed
289                  */
290                 *argptr++ = "--nw";
291                 *argptr++ = "--nx";
292                 *argptr++ = "--quiet";
293                 *argptr++ = "--batch";
294                 *argptr++ = "--command";
295                 *argptr++ = g_strdup_printf("%s", filespec);
296                 *argptr++ = "sylpheed"; /* program file name */
297                 *argptr++ = g_strdup_printf("%d", crash_pid);
298                 *argptr   = NULL;
299
300                 /*
301                  * redirect output to write end of pipe
302                  */
303                 close(1);
304                 dup(choutput[1]);
305                 close(choutput[0]);
306                 if (-1 == execvp("gdb", argp)) 
307                         puts("error execvp\n");
308         } else {
309                 char buf[100];
310                 int r;
311         
312                 waitpid(pid, NULL, 0);
313
314                 /*
315                  * make it non blocking
316                  */
317                 if (-1 == fcntl(choutput[0], F_SETFL, O_NONBLOCK))
318                         puts("set to non blocking failed\n");
319
320                 /*
321                  * get the output
322                  */
323                 do {
324                         r = read(choutput[0], buf, sizeof buf - 1);
325                         if (r > 0) {
326                                 buf[r] = 0;
327                                 g_string_append(string, buf);
328                         }
329                 } while (r > 0);
330                 
331                 close(choutput[0]);
332                 close(choutput[1]);
333                 
334                 /*
335                  * kill the process we attached to
336                  */
337                 kill(crash_pid, SIGCONT); 
338         }
339 }
340
341 /***/
342
343 /*
344  *\brief        features
345  */
346 static const gchar *get_compiled_in_features(void)
347 {
348         return g_strdup_printf(
349                    _("Compiled-in features:%s"),
350 #if HAVE_GDK_IMLIB
351                    " gdk_imlib"
352 #endif
353 #if HAVE_GDK_PIXBUF
354                    " gdk-pixbuf"
355 #endif
356 #if USE_THREADS
357                    " gthread"
358 #endif
359 #if INET6
360                    " IPv6"
361 #endif
362 #if HAVE_LIBCOMPFACE
363                    " libcompface"
364 #endif
365 #if HAVE_LIBJCONV
366                    " libjconv"
367 #endif
368 #if USE_GPGME
369                    " GPGME"
370 #endif
371 #if USE_SSL
372                    " SSL"
373 #endif
374 #if USE_LDAP
375                    " LDAP"
376 #endif
377 #if USE_JPILOT
378                    " JPilot"
379 #endif
380 #if USE_PSPELL
381                    " pspell"
382 #endif
383         "");
384 }
385
386 /***/
387
388 static const gchar *get_lib_version(void)
389 {
390 #if defined(__GNU_LIBRARY__)
391         return g_strdup_printf("GNU libc %s", gnu_get_libc_version());
392 #else
393         return g_strdup(_("Unknown"));
394 #endif
395 }
396
397 /***/
398
399 static const gchar *get_operating_system(void)
400 {
401 #if HAVE_SYS_UTSNAME_H
402         struct utsname utsbuf;
403         uname(&utsbuf);
404         return g_strdup_printf("%s %s (%s)",
405                                utsbuf.sysname,
406                                utsbuf.release,
407                                utsbuf.machine);
408 #else
409         return g_strdup(_("Unknown"));
410         
411 #endif
412 }
413
414 /***/
415
416 /*
417  *\brief        checks KDE, GNOME and Sylpheed specific variables
418  *              to see if the crash dialog is allowed (because some
419  *              developers may prefer to run sylpheed under gdb...)
420  */
421 static gboolean is_crash_dialog_allowed(void)
422 {
423         return getenv("KDE_DEBUG") || 
424                !getenv("GNOME_DISABLE_CRASH_DIALOG") ||
425                !getenv("SYLPHEED_NO_CRASH");
426 }
427
428 /*
429  *\brief        this handler will probably evolve into 
430  *              something better.
431  */
432 static void crash_handler(int sig)
433 {
434         pid_t pid;
435         static volatile unsigned long crashed_ = 0;
436
437         /*
438          * besides guarding entrancy it's probably also better 
439          * to mask off signals
440          */
441         if (crashed_) 
442                 return;
443
444         crashed_++;
445
446         /*
447          * gnome ungrabs focus, and flushes gdk. mmmh, good idea.
448          */
449         gdk_pointer_ungrab(GDK_CURRENT_TIME);
450         gdk_keyboard_ungrab(GDK_CURRENT_TIME);
451         gdk_flush();
452          
453         if (0 == (pid = fork())) {
454                 char buf[50];
455                 char *args[4];
456         
457                 /*
458                  * probably also some other parameters (like GTK+ ones).
459                  */
460                 args[0] = "--debug";
461                 args[1] = "--crash";
462                 sprintf(buf, "%ld,%d", getppid(), sig);
463                 args[2] = buf;
464                 args[3] = NULL;
465
466                 setgid(getgid());
467                 setuid(getuid());
468 #if 0
469                 execvp("/alfons/Projects/sylpheed-claws/src/sylpheed", args);
470 #else
471                 execvp("sylpheed", args);
472 #endif
473         } else {
474                 waitpid(pid, NULL, 0);
475                 _exit(253);
476         }
477
478         _exit(253);
479 }
480