ede/ecrasher/CrashDialog.cpp
2008-09-15 11:00:23 +00:00

287 lines
7.0 KiB
C++

/*
* $Id$
*
* Ecrasher, a crash handler tool
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2008 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
*/
#include "icons/core.xpm"
#include "CrashDialog.h"
#include <FL/Fl.H>
#include <FL/Fl_Pixmap.H>
#include <FL/Fl_File_Chooser.H>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <edelib/Nls.h>
#include <edelib/File.h>
#include <edelib/Directory.h>
#include <edelib/MessageBox.h>
#define DIALOG_W 380
#define DIALOG_H 130
#define DIALOG_W_EXPANDED 380
#define DIALOG_H_EXPANDED 340
int spawn_backtrace(const char* gdb_path, const char* program, const char* core, const char* output, const char* script) {
const char* gdb_script = "bt\nquit\n";
const int gdb_script_len = 8;
// file with gdb commands
int sfd = open(script, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(sfd == -1)
return -1;
write(sfd, gdb_script, gdb_script_len);
close(sfd);
// output file with gdb backtrace
int ofd = open(output, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(ofd == -1)
return -1;
pid_t pid = fork();
if(pid == -1) {
close(ofd);
return -1;
} else if(pid == 0) {
dup2(ofd, 1);
close(ofd);
char* argv[8];
argv[0] = (char*)gdb_path;
argv[1] = "--quiet";
argv[2] = "--batch";
argv[3] = "-x";
argv[4] = (char*)script;
argv[5] = (char*)program;
argv[6] = (char*)core;
argv[7] = 0;
execvp(argv[0], argv);
return -1;
} else {
int status;
if(waitpid(pid, &status, 0) != pid)
return -1;
}
return 0;
}
edelib::String get_uname(void) {
struct utsname ut;
uname(&ut);
edelib::String ret;
ret.printf("%s %s %s %s %s", ut.sysname, ut.nodename, ut.release, ut.version, ut.machine);
return ret;
}
void show_details_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->show_details();
}
void close_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->hide();
}
void save_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->save();
}
CrashDialog::CrashDialog() : Fl_Window(DIALOG_W, DIALOG_H, _("EDE crash handler")),
appname(NULL), apppath(NULL), bugaddress(NULL), pid(NULL), signal_num(NULL) {
details_shown = false;
begin();
pix = new Fl_Pixmap(core_xpm);
icon_box = new Fl_Box(10, 10, 70, 75);
icon_box->image(pix);
txt_box = new Fl_Box(85, 10, 285, 75);
txt_box->align(FL_ALIGN_WRAP | FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
close = new Fl_Button(280, 95, 90, 25, _("&Close"));
close->callback(close_cb, this);
details = new Fl_Button(10, 95, 265, 25, _("@> Show details"));
details->box(FL_FLAT_BOX);
details->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT);
details->callback(show_details_cb, this);
// widgets for expanded dialog
trace_log = new Fl_Text_Display(10, 130, 360, 165);
trace_buff = new Fl_Text_Buffer();
trace_log->buffer(trace_buff);
trace_log->hide();
save_as = new Fl_Button(280, 305, 90, 25, _("&Save As..."));
save_as->callback(save_cb, this);
save_as->hide();
end();
}
CrashDialog::~CrashDialog() {
// looks like fltk does not clean image() assigned data
delete pix;
}
void CrashDialog::show_details(void) {
if(trace_log->visible()) {
trace_log->hide();
save_as->hide();
details->label(_("@> Show details"));
size(DIALOG_W, DIALOG_H);
} else {
trace_log->show();
save_as->show();
details->label(_("@< Hide details"));
size(DIALOG_W_EXPANDED, DIALOG_H_EXPANDED);
if(!details_shown) {
trace_buff->remove(0, trace_buff->length());
edelib::String address = _("\nPlease report this at: ");
if(bugaddress)
address += bugaddress;
else
address += "bugs@equinox-project.org";
trace_buff->append(address.c_str());
trace_buff->append("\n\n");
trace_buff->append("---------- short summary ----------\n");
trace_buff->append("\nEDE version: 2.0");
trace_buff->append("\nSystem info: ");
trace_buff->append(get_uname().c_str());
trace_buff->append("\nProgram name: ");
if(appname)
trace_buff->append(appname);
else
trace_buff->append("(unknown)");
trace_buff->append("\nExecutable path: ");
if(apppath)
trace_buff->append(apppath);
else
trace_buff->append("(unknown)");
trace_buff->append("\nRunning PID: ");
if(pid)
trace_buff->append(pid);
else
trace_buff->append("(unknown)");
trace_buff->append("\nSignal received: ");
if(signal_num)
trace_buff->append(signal_num);
else
trace_buff->append("(unknown)");
// try backtrace via gdb
trace_buff->append("\n\n---------- backtrace ----------\n");
const char* core_file = "core";
if(!edelib::file_exists(core_file)) {
trace_buff->append("\nUnable to find 'core' file. Backtrace will not be done.");
details_shown = false;
return;
}
edelib::String gdb_path = edelib::file_path("gdb");
if(gdb_path.empty()) {
trace_buff->append("\nUnable to find gdb. Is it installed ?");
// set to false so next 'Show Details' click can try again with the debugger
details_shown = false;
return;
}
// check if we can write in /tmp; if not, try with $HOME
edelib::String dir = "/tmp";
if(!edelib::dir_writeable(dir.c_str())) {
dir = edelib::dir_home();
if(!edelib::dir_writeable(dir.c_str())) {
trace_buff->append("\nDon't have permissions to write either to /tmp or $HOME");
details_shown = false;
return;
}
}
edelib::String gdb_output, gdb_script;
gdb_output = gdb_script = dir;
// TODO: these files should be unique per session
gdb_output += "/.gdb_output";
gdb_script += "/.gdb_script";
if(spawn_backtrace(gdb_path.c_str(), cmd.c_str(), core_file, gdb_output.c_str(), gdb_script.c_str()) == -1) {
trace_buff->append("\nUnable to properly execute gdb");
details_shown = false;
return;
}
if(!edelib::file_exists(gdb_output.c_str())) {
trace_buff->append("\nStrange, can't find gdb output that I was just wrote to");
details_shown = false;
return;
}
trace_buff->appendfile(gdb_output.c_str());
edelib::file_remove(gdb_output.c_str());
edelib::file_remove(gdb_script.c_str());
edelib::file_remove(core_file);
details_shown = true;
}
}
}
void CrashDialog::save(void) {
const char* p = fl_file_chooser(_("Save details to..."), "Text Files (*.txt)\tAll Files(*)", "dump.txt");
if(!p)
return;
// so we can have EOL in file
trace_buff->append("\n");
if(trace_buff->savefile(p) != 0)
edelib::alert(_("Unable to save to %s. Please check permissions to write in this directory or file"), p);
}
void CrashDialog::run(void) {
edelib::String l;
if(appname || apppath) {
const char* p = (appname ? appname : apppath);
l.printf(_("Program '%s' just crashed !"), p);
} else
l += _("Program just crashed !");
l += _("\n\nYou can inspect details about this crash by clicking on 'Show details' below");
txt_box->copy_label(l.c_str());
if(!shown())
show();
while(shown())
Fl::wait();
}