Make plasma libs build.
[amarok.git] / src / amarokcore / crashhandler.cpp
blob3b4b9c932df1e8fdb3e585b97d6bb79b399de8f3
1 /***************************************************************************
2 * Copyright (C) 2005 Max Howell <max.howell@methylblue.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 ***************************************************************************/
11 #include "amarok.h"
12 #include "amarokconfig.h"
13 #include "crashhandler.h"
15 #include <kapplication.h> //invokeMailer()
16 #include <kdebug.h> //kBacktrace()
17 #include <kglobal.h>
18 #include <klocale.h>
19 #include <k3tempfile.h>
20 #include <ktoolinvocation.h>
22 #include <QFile>
23 #include <QRegExp>
24 #include <q3textstream.h>
25 #include <qglobal.h> //qVersion()
26 #include <QByteArray>
29 #include <cstdio> //popen, fread
30 #include <iostream>
31 #include <sys/types.h> //pid_t
32 #include <sys/wait.h> //waitpid
33 #include <taglib.h>
34 #include <unistd.h> //write, getpid
35 #include <string.h>
37 #ifndef Q_WS_WIN
38 #include <pthread.h>
39 #endif
42 #ifndef TAGLIB_PATCH_VERSION
43 // seems to be wheel's style
44 #define TAGLIB_PATCH_VERSION 0
45 #endif
47 #ifndef Q_WS_WIN
49 namespace Amarok
51 #if 0
52 class CrashHandlerWidget : public KDialog {
53 public:
54 CrashHandlerWidget();
56 #endif
58 static QString
59 runCommand( const QByteArray &command )
61 static const uint SIZE = 40960; //40 KiB
62 static char stdoutBuf[ SIZE ] = {0};
64 // std::cout << "Running: " << command << std::endl;
66 FILE *process = ::popen( command, "r" );
67 if ( process )
69 stdoutBuf[ std::fread( static_cast<void*>( stdoutBuf ), sizeof(char), SIZE-1, process ) ] = '\0';
70 ::pclose( process );
72 return QString::fromLocal8Bit( stdoutBuf );
75 void
76 Crash::crashHandler( int /*signal*/ )
78 // we need to fork to be able to get a
79 // semi-decent bt - I dunno why
80 const pid_t pid = ::fork();
82 if( pid < 0 )
84 std::cout << "forking crash reporter failed\n";
85 // continuing now can't do no good
86 _exit( 1 );
88 else if ( pid == 0 )
90 // we are the child process (the result of the fork)
91 std::cout << "Amarok is crashing!\n";
93 QString subject = APP_VERSION " ";
94 QString body = i18n(
95 "Amarok has crashed! We are terribly sorry about this :(\n\n"
96 "But, all is not lost! You could potentially help us fix the crash. "
97 "Information describing the crash is below, so just click send, "
98 "or if you have time, write a brief description of how the crash happened first.\n\n"
99 "Many thanks.\n\n" );
100 body += i18n( "\n\n\n\n\n\n"
101 "The information below is to help the developers identify the problem, "
102 "please do not modify it.\n\n\n\n" );
105 body += "======== DEBUG INFORMATION =======\n"
106 "Version: " APP_VERSION "\n"
107 "Engine: %1\n"
108 "Build date: " __DATE__ "\n"
109 "CC version: " __VERSION__ "\n" //assuming we're using GCC
110 "KDElibs: " KDE_VERSION_STRING "\n"
111 "Qt: %2\n"
112 "TagLib: %3.%4.%5\n"
113 "CPU count: %6\n";
115 QString cpucount = "unknown";
116 #ifdef __linux__
117 QString line;
118 uint cpuCount = 0;
119 QFile cpuinfo( "/proc/cpuinfo" );
120 if ( cpuinfo.open( QIODevice::ReadOnly ) ) {
121 char cline[1024];
122 while ( cpuinfo.readLine( cline, sizeof(cline) ) != -1 ) {
123 line = cline;
124 if ( line.startsWith( "processor" ) ) {
125 ++cpuCount;
129 cpucount = QString::number( cpuCount );
130 #endif
132 KConfigGroup config = KGlobal::config()->group( "Playback" );
133 QString soundSystem = config.readEntry( "Sound System", QString() );
134 body = body.arg( soundSystem )
135 .arg( qVersion() )
136 .arg( TAGLIB_MAJOR_VERSION )
137 .arg( TAGLIB_MINOR_VERSION )
138 .arg( TAGLIB_PATCH_VERSION )
139 .arg( cpucount );
141 #ifdef NDEBUG
142 body += "NDEBUG: true";
143 #endif
144 body += '\n';
146 /// obtain the backtrace with gdb
148 K3TempFile temp;
149 temp.setAutoDelete( true );
151 const int handle = temp.handle();
153 // QCString gdb_command_string =
154 // "file amarok\n"
155 // "attach " + QCString().setNum( ::getppid() ) + "\n"
156 // "bt\n" "echo \\n\n"
157 // "thread apply all bt\n";
159 const QByteArray gdb_batch =
160 "bt\n"
161 // "echo \\n\\n\n"
162 // "bt full\n"
163 "echo \\n\\n\n"
164 "echo ==== (gdb) thread apply all bt ====\\n\n"
165 "thread apply all bt\n";
167 ::write( handle, gdb_batch, gdb_batch.length() );
168 ::fsync( handle );
170 // so we can read stderr too
171 ::dup2( fileno( stdout ), fileno( stderr ) );
173 QByteArray gdb;
174 gdb = "gdb --nw -n --batch -x ";
175 gdb += temp.name().toLatin1();
176 gdb += " amarok ";
177 gdb += QByteArray().setNum( ::getppid() );
179 QString bt = runCommand( gdb );
181 /// clean up
182 bt.remove( "(no debugging symbols found)..." );
183 bt.remove( "(no debugging symbols found)\n" );
184 bt.replace( QRegExp("\n{2,}"), "\n" ); //clean up multiple \n characters
185 bt.trimmed();
187 /// analyze usefulness
188 bool useful = true;
189 const QString fileCommandOutput = runCommand( "file `which amarok`" );
191 if( fileCommandOutput.indexOf( "not stripped", false ) == -1 )
192 subject += "[___stripped]"; //same length as below
193 else
194 subject += "[NOTstripped]";
196 if( !bt.isEmpty() ) {
197 const int invalidFrames = bt.count( QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?") );
198 const int validFrames = bt.count( QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]") );
199 const int totalFrames = invalidFrames + validFrames;
201 if( totalFrames > 0 ) {
202 const double validity = double(validFrames) / totalFrames;
203 subject += QString("[validity: %1]").arg( validity, 0, 'f', 2 );
204 if( validity <= 0.5 ) useful = false;
206 subject += QString("[frames: %1]").arg( totalFrames, 3 /*padding*/ );
208 if( bt.indexOf( QRegExp(" at \\w*\\.cpp:\\d+\n") ) >= 0 )
209 subject += "[line numbers]";
211 else
212 useful = false;
214 subject += QString("[%1]").arg( soundSystem.remove( QRegExp("-?engine") ) );
216 std::cout << subject.toLatin1().constData() << std::endl;
219 //TODO -fomit-frame-pointer buggers up the backtrace, so detect it
220 //TODO -O optimization can rearrange execution and stuff so show a warning for the developer
221 //TODO pass the CXXFLAGS used with the email
223 if( useful ) {
224 body += "==== file `which amarok` =======\n";
225 body += fileCommandOutput + "\n\n";
226 body += "==== (gdb) bt =====================\n";
227 body += bt + "\n\n";
228 // body += "==== kBacktrace() ================\n";
229 // body += kBacktrace();
231 //TODO startup notification
232 // KToolInvocation::invokeMailer(
233 // /*to*/ "amarok-backtraces@lists.sf.net",
234 // /*cc*/ QString(),
235 // /*bcc*/ QString(),
236 // /*subject*/ subject,
237 // /*body*/ body,
238 // /*messageFile*/ QString(),
239 // /*attachURLs*/ QStringList(),
240 // /*startup_id*/ "" );
241 std::cout << body.toLatin1().data();
243 else {
244 std::cout << i18n( "\nAmarok has crashed! We are terribly sorry about this :(\n\n"
245 "But, all is not lost! Perhaps an upgrade is already available "
246 "which fixes the problem. Please check your distribution's software repository.\n" ).toLocal8Bit().constData();
249 //_exit() exits immediately, otherwise this
250 //function is called repeatedly ad finitum
251 ::_exit( 255 );
254 else {
255 // we are the process that crashed
257 ::alarm( 0 );
259 // wait for child to exit
260 ::waitpid( pid, NULL, 0 );
261 ::_exit( 253 );
267 #if 0
269 #include <QLabel>
270 #include <QLayout>
271 #include <kvbox.h>
272 #include <kdialog.h>
273 #include <kpushbutton.h>
274 #include <kstdguiitem.h>
275 #include <kstandarddirs.h>
277 Amarok::CrashHandlerWidget::CrashHandlerWidget()
279 QBoxLayout *layout = new QHBoxLayout( this, 18, 12 );
282 QVBoxLayout *lay = new QVBoxLayout( layout );
283 QLabel *label = new QLabel( this );
284 label->setPixmap( KStandardDirs::locate( "data", "drkonqi/pics/konqi.png" ) );
285 label->setFrameStyle( QFrame::Plain | QFrame::Box );
286 lay->add( label );
287 lay->addItem( new QSpacerItem( 3, 3, QSizePolicy::Minimum, QSizePolicy::Expanding ) );
290 layout = new QVBoxLayout( layout, 6 );
292 layout->add( new QLabel( /*i18n*/(
293 "<p>" "Amarok has crashed! We are terribly sorry about this :("
294 "<p>" "However you now have an opportunity to help us fix this crash so that it doesn't "
295 "happen again! Click <b>Send Email</b> and Amarok will prepare an email that you "
296 "can send to us that contains information about the crash, and we'll try to fix it "
297 "as soon as possible."
298 "<p>" "Thanks for choosing Amarok.<br>" ), this ) );
300 layout = new QHBoxLayout( layout, 6 );
302 layout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Expanding ) );
303 layout->add( new KPushButton( KGuiItem( i18n("Send Email"), "mail_send" ), this, "email" ) );
304 layout->add( new KPushButton( KStandardGuiItem::close(), this, "close" ) );
306 child<QPushButton*>("email")->setDefault( true );
308 connect( child( "email" ), SIGNAL(clicked()), SLOT(accept()) );
309 connect( child( "close" ), SIGNAL(clicked()), SLOT(reject()) );
311 setCaption( i18n("Crash Handler") );
312 setFixedSize( sizeHint() );
314 #endif
315 #endif