Warning, file /plasma/drkonqi/src/drkonqi.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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", "Select Filename")); 0157 dlg->setAcceptMode(QFileDialog::AcceptSave); 0158 dlg->setFileMode(QFileDialog::AnyFile); 0159 dlg->setOption(QFileDialog::DontResolveSymlinks, false); 0160 if (dlg->exec() != QDialog::Accepted) { 0161 return; 0162 } 0163 0164 if (!dlg) { 0165 // Dialog is invalid, it was probably deleted (ex. via DBus call) 0166 // return and do not crash 0167 return; 0168 } 0169 0170 QUrl fileUrl; 0171 if (!dlg->selectedUrls().isEmpty()) 0172 fileUrl = dlg->selectedUrls().first(); 0173 delete dlg; 0174 0175 if (fileUrl.isValid()) { 0176 QTemporaryFile tf; 0177 if (tf.open()) { 0178 QTextStream ts(&tf); 0179 ts << reportText; 0180 ts.flush(); 0181 } else { 0182 KMessageBox::error(parent, 0183 xi18nc("@info", 0184 "Cannot open file <filename>%1</filename> " 0185 "for writing.", 0186 tf.fileName())); 0187 return; 0188 } 0189 0190 // QFileDialog was run with confirmOverwrite, so we can safely 0191 // overwrite as necessary. 0192 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tf.fileName()), fileUrl, -1, KIO::DefaultFlags | KIO::Overwrite); 0193 KJobWidgets::setWindow(job, parent); 0194 if (!job->exec()) { 0195 KMessageBox::error(parent, job->errorString()); 0196 } 0197 } 0198 } 0199 } 0200 0201 // Helper functions for the shutdownSaveReport 0202 class ShutdownHelper : public QObject 0203 { 0204 Q_OBJECT 0205 public: 0206 QString shutdownSaveString; 0207 0208 void removeOldFilesIn(QDir &dir) 0209 { 0210 auto fileList = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::SortFlag::Time | QDir::Reversed); 0211 for (int i = fileList.size(); i >= 10; i--) { 0212 auto currentFile = fileList.takeFirst(); 0213 dir.remove(currentFile.fileName()); 0214 } 0215 } 0216 0217 void saveReportAndQuit() 0218 { 0219 const QString dirname = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 0220 // Try to create the directory to save the logs, if we can't open the directory, 0221 // just bail out. no need to hold the shutdown process. 0222 QDir dir(dirname); 0223 if (!dir.mkpath(dirname)) { 0224 qApp->quit(); 0225 } 0226 0227 removeOldFilesIn(dir); 0228 const QString defname = dirname + QLatin1Char('/') + QStringLiteral("pid-") + QString::number(DrKonqi::pid()) + QLatin1Char('-') 0229 + getSuggestedKCrashFilename(DrKonqi::crashedApplication()); 0230 0231 QFile shutdownSaveFile(defname); 0232 if (shutdownSaveFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 0233 QTextStream ts(&shutdownSaveFile); 0234 ts << shutdownSaveString; 0235 ts.flush(); 0236 shutdownSaveFile.close(); 0237 } 0238 deleteLater(); 0239 qApp->quit(); 0240 } 0241 0242 void appendNewLine(const QString &newLine) 0243 { 0244 shutdownSaveString += newLine; 0245 } 0246 }; 0247 0248 void DrKonqi::shutdownSaveReport() 0249 { 0250 if (!DrKonqi::isEphemeralCrash()) { // No need to make a backtrace if the crash isn't ephemeral (e.g. from coredumpd) 0251 qApp->quit(); 0252 return; 0253 } 0254 0255 auto btGenerator = instance()->debuggerManager()->backtraceGenerator(); 0256 auto shutdownHelper = new ShutdownHelper(); 0257 QObject::connect(btGenerator, &BacktraceGenerator::done, shutdownHelper, &ShutdownHelper::saveReportAndQuit); 0258 QObject::connect(btGenerator, &BacktraceGenerator::someError, shutdownHelper, &ShutdownHelper::saveReportAndQuit); 0259 QObject::connect(btGenerator, &BacktraceGenerator::failedToStart, shutdownHelper, &ShutdownHelper::saveReportAndQuit); 0260 QObject::connect(btGenerator, &BacktraceGenerator::newLine, shutdownHelper, &ShutdownHelper::appendNewLine); 0261 btGenerator->start(); 0262 } 0263 0264 void DrKonqi::setSignal(int signal) 0265 { 0266 instance()->m_signal = signal; 0267 } 0268 0269 void DrKonqi::setAppName(const QString &appName) 0270 { 0271 instance()->m_appName = appName; 0272 } 0273 0274 void DrKonqi::setAppPath(const QString &appPath) 0275 { 0276 instance()->m_appPath = appPath; 0277 } 0278 0279 void DrKonqi::setAppVersion(const QString &appVersion) 0280 { 0281 instance()->m_appVersion = appVersion; 0282 } 0283 0284 void DrKonqi::setBugAddress(const QString &bugAddress) 0285 { 0286 instance()->m_bugAddress = bugAddress; 0287 } 0288 0289 void DrKonqi::setProgramName(const QString &programName) 0290 { 0291 instance()->m_programName = programName; 0292 } 0293 0294 void DrKonqi::setProductName(const QString &productName) 0295 { 0296 instance()->m_productName = productName; 0297 } 0298 0299 void DrKonqi::setPid(int pid) 0300 { 0301 instance()->m_pid = pid; 0302 } 0303 0304 void DrKonqi::setKdeinit(bool kdeinit) 0305 { 0306 instance()->m_kdeinit = kdeinit; 0307 } 0308 0309 void DrKonqi::setSafer(bool safer) 0310 { 0311 instance()->m_safer = safer; 0312 } 0313 0314 void DrKonqi::setRestarted(bool restarted) 0315 { 0316 instance()->m_restarted = restarted; 0317 } 0318 0319 void DrKonqi::setKeepRunning(bool keepRunning) 0320 { 0321 instance()->m_keepRunning = keepRunning; 0322 } 0323 0324 void DrKonqi::setThread(int thread) 0325 { 0326 instance()->m_thread = thread; 0327 } 0328 0329 void DrKonqi::setStartupId(const QString &startupId) 0330 { 0331 instance()->m_startupId = startupId; 0332 } 0333 0334 int DrKonqi::signal() 0335 { 0336 return instance()->m_signal; 0337 } 0338 0339 const QString &DrKonqi::appName() 0340 { 0341 return instance()->m_appName; 0342 } 0343 0344 const QString &DrKonqi::appPath() 0345 { 0346 return instance()->m_appPath; 0347 } 0348 0349 const QString &DrKonqi::appVersion() 0350 { 0351 return instance()->m_appVersion; 0352 } 0353 0354 const QString &DrKonqi::bugAddress() 0355 { 0356 return instance()->m_bugAddress; 0357 } 0358 0359 const QString &DrKonqi::programName() 0360 { 0361 return instance()->m_programName; 0362 } 0363 0364 const QString &DrKonqi::productName() 0365 { 0366 return instance()->m_productName; 0367 } 0368 0369 int DrKonqi::pid() 0370 { 0371 return instance()->m_pid; 0372 } 0373 0374 bool DrKonqi::isKdeinit() 0375 { 0376 return instance()->m_kdeinit; 0377 } 0378 0379 bool DrKonqi::isSafer() 0380 { 0381 return instance()->m_safer; 0382 } 0383 0384 bool DrKonqi::isRestarted() 0385 { 0386 return instance()->m_restarted; 0387 } 0388 0389 bool DrKonqi::isKeepRunning() 0390 { 0391 return instance()->m_keepRunning; 0392 } 0393 0394 int DrKonqi::thread() 0395 { 0396 return instance()->m_thread; 0397 } 0398 0399 bool DrKonqi::ignoreQuality() 0400 { 0401 static bool ignore = qEnvironmentVariableIsSet("DRKONQI_IGNORE_QUALITY") || qEnvironmentVariableIsSet("DRKONQI_TEST_MODE"); 0402 return ignore; 0403 } 0404 0405 bool DrKonqi::isTestingBugzilla() 0406 { 0407 return kdeBugzillaURL().contains(QLatin1String("bugstest.kde.org")); 0408 } 0409 0410 const QString &DrKonqi::kdeBugzillaURL() 0411 { 0412 // WARNING: for practical reasons this cannot use the shared instance 0413 // Initing the instances requires knowing the URL already, so we'd have 0414 // an init loop. Use a local static instead. Otherwise we'd crash on 0415 // initialization of global statics derived from our return value. 0416 // Always copy into the local static and return that! 0417 static QString url; 0418 if (!url.isEmpty()) { 0419 return url; 0420 } 0421 0422 url = QString::fromLocal8Bit(qgetenv("DRKONQI_KDE_BUGZILLA_URL")); 0423 if (!url.isEmpty()) { 0424 return url; 0425 } 0426 0427 if (qEnvironmentVariableIsSet("DRKONQI_TEST_MODE")) { 0428 url = QStringLiteral("https://bugstest.kde.org/"); 0429 } else { 0430 url = QStringLiteral("https://bugs.kde.org/"); 0431 } 0432 0433 return url; 0434 } 0435 0436 const QString &DrKonqi::startupId() 0437 { 0438 return instance()->m_startupId; 0439 } 0440 0441 QString DrKonqi::backendClassName() 0442 { 0443 return QString::fromLatin1(instance()->m_backend->metaObject()->className()); 0444 } 0445 0446 bool DrKonqi::isEphemeralCrash() 0447 { 0448 #ifdef SYSTEMD_AVAILABLE 0449 return qobject_cast<CoredumpBackend *>(instance()->m_backend) == nullptr; // not coredumpd backend => ephemeral 0450 #else 0451 return true; 0452 #endif 0453 } 0454 0455 void DrKonqi::cleanupBeforeQuit() 0456 { 0457 instance()->m_backend->cleanup(); 0458 } 0459 0460 #include "drkonqi.moc"