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"