File indexing completed on 2024-04-21 05:26:42
0001 /* 0002 SPDX-FileCopyrightText: 2000-2003 Hans Petter Bieker <bieker@kde.org> 0003 SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 #include "drkonqi.h" 0009 #include "drkonqi_debug.h" 0010 0011 #include <chrono> 0012 0013 #include <QFileDialog> 0014 #include <QPointer> 0015 #include <QTemporaryFile> 0016 #include <QTextStream> 0017 #include <QTimerEvent> 0018 0019 #include <KCrash> 0020 #include <KJobWidgets> 0021 #include <KLocalizedString> 0022 #include <KMessageBox> 0023 #include <QApplication> 0024 #include <kio/filecopyjob.h> 0025 0026 #include "backtracegenerator.h" 0027 #include "crashedapplication.h" 0028 #include "debuggermanager.h" 0029 #include "drkonqibackends.h" 0030 #include "systeminformation.h" 0031 0032 #ifdef SYSTEMD_AVAILABLE 0033 #include "coredumpbackend.h" 0034 #endif 0035 0036 using namespace std::chrono_literals; 0037 0038 static AbstractDrKonqiBackend *factorizeBackend() 0039 { 0040 // This is controlled by the environment because doing it as a cmdline option is supremely horrible because 0041 // DrKonqi is a singleton that gets created at a random point in time, while options are only set on it afterwards. 0042 // Since we don't want a nullptr backend we'll need the backend factorization to be independent of the cmdline. 0043 // This could maybe be changed but would require substantial rejiggering of the singleton to not have points in 0044 // time where there is no backend behind it. 0045 #ifdef SYSTEMD_AVAILABLE 0046 if (qgetenv("DRKONQI_BACKEND") == QByteArrayLiteral("COREDUMPD")) { 0047 qunsetenv("DRKONQI_BACKEND"); 0048 return new CoredumpBackend; 0049 } 0050 #endif 0051 return new KCrashBackend(); 0052 } 0053 0054 DrKonqi::DrKonqi() 0055 : m_systemInformation(new SystemInformation()) 0056 , m_backend(factorizeBackend()) 0057 , m_signal(0) 0058 , m_pid(0) 0059 , m_kdeinit(false) 0060 , m_safer(false) 0061 , m_restarted(false) 0062 , m_keepRunning(false) 0063 , m_thread(0) 0064 { 0065 } 0066 0067 DrKonqi::~DrKonqi() 0068 { 0069 delete m_systemInformation; 0070 delete m_backend; 0071 } 0072 0073 // static 0074 DrKonqi *DrKonqi::instance() 0075 { 0076 static DrKonqi drKonqiInstance; 0077 return &drKonqiInstance; 0078 } 0079 0080 // based on KCrashDelaySetHandler from kdeui/util/kcrash.cpp 0081 class EnableCrashCatchingDelayed : public QObject 0082 { 0083 public: 0084 EnableCrashCatchingDelayed() 0085 { 0086 startTimer(10s); 0087 } 0088 0089 protected: 0090 void timerEvent(QTimerEvent *event) override 0091 { 0092 qCDebug(DRKONQI_LOG) << "Enabling drkonqi crash catching"; 0093 KCrash::setDrKonqiEnabled(true); 0094 killTimer(event->timerId()); 0095 this->deleteLater(); 0096 } 0097 }; 0098 0099 bool DrKonqi::init() 0100 { 0101 if (!instance()->m_backend->init()) { 0102 return false; 0103 } else { // all ok, continue initialization 0104 // Set drkonqi to handle its own crashes, but only if the crashed app 0105 // is not drkonqi already. If it is drkonqi, delay enabling crash catching 0106 // to prevent recursive crashes (in case it crashes at startup) 0107 if (crashedApplication()->fakeExecutableBaseName() != QLatin1String("drkonqi")) { 0108 qCDebug(DRKONQI_LOG) << "Enabling drkonqi crash catching"; 0109 KCrash::setDrKonqiEnabled(true); 0110 } else { 0111 new EnableCrashCatchingDelayed; 0112 } 0113 return true; 0114 } 0115 } 0116 0117 // static 0118 SystemInformation *DrKonqi::systemInformation() 0119 { 0120 return instance()->m_systemInformation; 0121 } 0122 0123 // static 0124 DebuggerManager *DrKonqi::debuggerManager() 0125 { 0126 return instance()->m_backend->debuggerManager(); 0127 } 0128 0129 // static 0130 CrashedApplication *DrKonqi::crashedApplication() 0131 { 0132 return instance()->m_backend->crashedApplication(); 0133 } 0134 0135 // static 0136 void DrKonqi::saveReport(const QString &reportText, QWidget *parent) 0137 { 0138 if (isSafer()) { 0139 QTemporaryFile tf; 0140 tf.setFileTemplate(QStringLiteral("XXXXXX.kcrash")); 0141 tf.setAutoRemove(false); 0142 0143 if (tf.open()) { 0144 QTextStream textStream(&tf); 0145 textStream << reportText; 0146 textStream.flush(); 0147 KMessageBox::information(parent, xi18nc("@info", "Report saved to <filename>%1</filename>.", tf.fileName())); 0148 } else { 0149 KMessageBox::error(parent, i18nc("@info", "Could not create a file in which to save the report.")); 0150 } 0151 } else { 0152 QString defname = getSuggestedKCrashFilename(crashedApplication()); 0153 0154 QPointer<QFileDialog> dlg(new QFileDialog(parent, defname)); 0155 dlg->selectFile(defname); 0156 dlg->setWindowTitle(i18nc("@title:window", "Save Report")); 0157 dlg->setAcceptMode(QFileDialog::AcceptSave); 0158 dlg->setFileMode(QFileDialog::AnyFile); 0159 dlg->setOption(QFileDialog::DontResolveSymlinks, false); 0160 dlg->setDirectory(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).last()); 0161 if (dlg->exec() != QDialog::Accepted) { 0162 return; 0163 } 0164 0165 if (!dlg) { 0166 // Dialog is invalid, it was probably deleted (ex. via DBus call) 0167 // return and do not crash 0168 return; 0169 } 0170 0171 QUrl fileUrl; 0172 if (!dlg->selectedUrls().isEmpty()) 0173 fileUrl = dlg->selectedUrls().first(); 0174 delete dlg; 0175 0176 if (fileUrl.isValid()) { 0177 QTemporaryFile tf; 0178 if (tf.open()) { 0179 QTextStream ts(&tf); 0180 ts << reportText; 0181 ts.flush(); 0182 } else { 0183 KMessageBox::error(parent, 0184 xi18nc("@info", 0185 "Cannot open file <filename>%1</filename> " 0186 "for writing.", 0187 tf.fileName())); 0188 return; 0189 } 0190 0191 // QFileDialog was run with confirmOverwrite, so we can safely 0192 // overwrite as necessary. 0193 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tf.fileName()), fileUrl, -1, KIO::DefaultFlags | KIO::Overwrite); 0194 KJobWidgets::setWindow(job, parent); 0195 if (!job->exec()) { 0196 KMessageBox::error(parent, job->errorString()); 0197 } 0198 } 0199 } 0200 } 0201 0202 void DrKonqi::setSignal(int signal) 0203 { 0204 instance()->m_signal = signal; 0205 } 0206 0207 void DrKonqi::setAppName(const QString &appName) 0208 { 0209 instance()->m_appName = appName; 0210 } 0211 0212 void DrKonqi::setAppPath(const QString &appPath) 0213 { 0214 instance()->m_appPath = appPath; 0215 } 0216 0217 void DrKonqi::setAppVersion(const QString &appVersion) 0218 { 0219 instance()->m_appVersion = appVersion; 0220 } 0221 0222 void DrKonqi::setBugAddress(const QString &bugAddress) 0223 { 0224 instance()->m_bugAddress = bugAddress; 0225 } 0226 0227 void DrKonqi::setProgramName(const QString &programName) 0228 { 0229 instance()->m_programName = programName; 0230 } 0231 0232 void DrKonqi::setProductName(const QString &productName) 0233 { 0234 instance()->m_productName = productName; 0235 } 0236 0237 void DrKonqi::setPid(int pid) 0238 { 0239 instance()->m_pid = pid; 0240 } 0241 0242 void DrKonqi::setKdeinit(bool kdeinit) 0243 { 0244 instance()->m_kdeinit = kdeinit; 0245 } 0246 0247 void DrKonqi::setSafer(bool safer) 0248 { 0249 instance()->m_safer = safer; 0250 } 0251 0252 void DrKonqi::setRestarted(bool restarted) 0253 { 0254 instance()->m_restarted = restarted; 0255 } 0256 0257 void DrKonqi::setKeepRunning(bool keepRunning) 0258 { 0259 instance()->m_keepRunning = keepRunning; 0260 } 0261 0262 void DrKonqi::setThread(int thread) 0263 { 0264 instance()->m_thread = thread; 0265 } 0266 0267 void DrKonqi::setStartupId(const QString &startupId) 0268 { 0269 instance()->m_startupId = startupId; 0270 } 0271 0272 int DrKonqi::signal() 0273 { 0274 return instance()->m_signal; 0275 } 0276 0277 const QString &DrKonqi::appName() 0278 { 0279 return instance()->m_appName; 0280 } 0281 0282 const QString &DrKonqi::appPath() 0283 { 0284 return instance()->m_appPath; 0285 } 0286 0287 const QString &DrKonqi::appVersion() 0288 { 0289 return instance()->m_appVersion; 0290 } 0291 0292 const QString &DrKonqi::bugAddress() 0293 { 0294 return instance()->m_bugAddress; 0295 } 0296 0297 const QString &DrKonqi::programName() 0298 { 0299 return instance()->m_programName; 0300 } 0301 0302 const QString &DrKonqi::productName() 0303 { 0304 return instance()->m_productName; 0305 } 0306 0307 int DrKonqi::pid() 0308 { 0309 return instance()->m_pid; 0310 } 0311 0312 bool DrKonqi::isKdeinit() 0313 { 0314 return instance()->m_kdeinit; 0315 } 0316 0317 bool DrKonqi::isSafer() 0318 { 0319 return instance()->m_safer; 0320 } 0321 0322 bool DrKonqi::isRestarted() 0323 { 0324 return instance()->m_restarted; 0325 } 0326 0327 bool DrKonqi::isKeepRunning() 0328 { 0329 return instance()->m_keepRunning; 0330 } 0331 0332 int DrKonqi::thread() 0333 { 0334 return instance()->m_thread; 0335 } 0336 0337 bool DrKonqi::ignoreQuality() 0338 { 0339 static bool ignore = qEnvironmentVariableIsSet("DRKONQI_TEST_MODE") && qEnvironmentVariableIntValue("DRKONQI_IGNORE_QUALITY") == 1; 0340 return ignore; 0341 } 0342 0343 bool DrKonqi::isTestingBugzilla() 0344 { 0345 return kdeBugzillaURL().contains(QLatin1String("bugstest.kde.org")); 0346 } 0347 0348 const QString &DrKonqi::kdeBugzillaURL() 0349 { 0350 // WARNING: for practical reasons this cannot use the shared instance 0351 // Initing the instances requires knowing the URL already, so we'd have 0352 // an init loop. Use a local static instead. Otherwise we'd crash on 0353 // initialization of global statics derived from our return value. 0354 // Always copy into the local static and return that! 0355 static QString url; 0356 if (!url.isEmpty()) { 0357 return url; 0358 } 0359 0360 url = QString::fromLocal8Bit(qgetenv("DRKONQI_KDE_BUGZILLA_URL")); 0361 if (!url.isEmpty()) { 0362 return url; 0363 } 0364 0365 if (qEnvironmentVariableIsSet("DRKONQI_TEST_MODE")) { 0366 url = QStringLiteral("https://bugstest.kde.org/"); 0367 } else { 0368 url = QStringLiteral("https://bugs.kde.org/"); 0369 } 0370 0371 return url; 0372 } 0373 0374 const QString &DrKonqi::startupId() 0375 { 0376 return instance()->m_startupId; 0377 } 0378 0379 QString DrKonqi::backendClassName() 0380 { 0381 return QString::fromLatin1(instance()->m_backend->metaObject()->className()); 0382 } 0383 0384 bool DrKonqi::isEphemeralCrash() 0385 { 0386 #ifdef SYSTEMD_AVAILABLE 0387 return qobject_cast<CoredumpBackend *>(instance()->m_backend) == nullptr; // not coredumpd backend => ephemeral 0388 #else 0389 return true; 0390 #endif 0391 } 0392 0393 bool DrKonqi::minimalMode() 0394 { 0395 return qEnvironmentVariableIntValue("DRKONQI_MINIMAL_MODE") > 0; 0396 } 0397 0398 #include "drkonqi.moc"