File indexing completed on 2024-04-21 05:26:43

0001 /*****************************************************************
0002  * drkonqi - The KDE Crash Handler
0003  *
0004  * SPDX-FileCopyrightText: 2000-2003 Hans Petter Bieker <bieker@kde.org>
0005  * SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0006  *
0007  * SPDX-License-Identifier: BSD-2-Clause
0008  *****************************************************************/
0009 
0010 #include <chrono>
0011 #include <csignal>
0012 #include <cstdlib>
0013 #include <unistd.h>
0014 
0015 #include <QCommandLineParser>
0016 #include <QDBusConnection>
0017 #include <QDBusInterface>
0018 #include <QDBusReply>
0019 #include <QDebug>
0020 #include <QFile>
0021 #include <QIcon>
0022 #include <QTemporaryFile>
0023 #include <QTimer>
0024 
0025 #include <KAboutData>
0026 #include <KConfigGroup>
0027 #include <KLocalizedString>
0028 #include <KSharedConfig>
0029 #include <KSignalHandler>
0030 #ifdef Q_OS_MACOS
0031 #include <KWindowSystem>
0032 #endif
0033 
0034 #include <config-drkonqi.h>
0035 
0036 #include "backtracegenerator.h"
0037 #include "bugzillaintegration/reportinterface.h"
0038 #include "debuggermanager.h"
0039 #include "drkonqi.h"
0040 #include "drkonqidialog.h"
0041 #include "statusnotifier.h"
0042 
0043 using namespace std::chrono_literals;
0044 using namespace Qt::StringLiterals;
0045 
0046 static const char version[] = PROJECT_VERSION;
0047 
0048 namespace
0049 {
0050 
0051 void aboutToQuit()
0052 {
0053     if (ReportInterface::self()->hasCrashEventSent() || DrKonqi::debuggerManager()->backtraceGenerator()->hasAnyFailure()) {
0054         qApp->quit();
0055     } else {
0056         // Add a fallback timer. This timer makes sure that drkonqi will definitely quit, even if it should
0057         // have some sort of runtime defect that prevents reporting from finishing (and consequently not quitting).
0058         static QTimer timer;
0059         timer.setInterval(5min); // arbitrary time limit for trace+submission
0060         QObject::connect(&timer, &QTimer::timeout, qApp, &QCoreApplication::quit);
0061         QObject::connect(ReportInterface::self(), &ReportInterface::crashEventSent, qApp, &QCoreApplication::quit);
0062         QObject::connect(DrKonqi::debuggerManager()->backtraceGenerator(), &BacktraceGenerator::stateChanged, qApp, [] {
0063             if (DrKonqi::debuggerManager()->backtraceGenerator()->hasAnyFailure()) {
0064                 qApp->quit();
0065             }
0066         });
0067         ReportInterface::self()->setSendWhenReady(true);
0068         timer.start();
0069     }
0070 }
0071 
0072 void openDrKonqiDialog(DrKonqiDialog::GoTo to = DrKonqiDialog::GoTo::Main)
0073 {
0074     auto *w = new DrKonqiDialog();
0075     QObject::connect(qApp, &QCoreApplication::aboutToQuit, w, &QObject::deleteLater);
0076     QObject::connect(qApp, &QGuiApplication::lastWindowClosed, qApp, &aboutToQuit);
0077     w->show(to);
0078 #ifdef Q_OS_MACOS
0079     KWindowSystem::forceActiveWindow(w->winId());
0080 #endif
0081 }
0082 
0083 void requestDrKonqiDialog(bool restarted, bool interactionAllowed)
0084 {
0085     auto activation = interactionAllowed ? StatusNotifier::Activation::Allowed : StatusNotifier::Activation::NotAllowed;
0086     if (ReportInterface::self()->isCrashEventSendingEnabled()) {
0087         activation = StatusNotifier::Activation::AlreadySubmitting;
0088         ReportInterface::self()->setSendWhenReady(true);
0089         if (DrKonqi::debuggerManager()->backtraceGenerator()->state() == BacktraceGenerator::NotLoaded) {
0090             DrKonqi::debuggerManager()->backtraceGenerator()->start();
0091         }
0092     }
0093 
0094     auto *statusNotifier = new StatusNotifier();
0095     if (interactionAllowed) {
0096         statusNotifier->show();
0097     }
0098     if (!restarted) {
0099         statusNotifier->notify(activation);
0100     }
0101     QObject::connect(statusNotifier, &StatusNotifier::expired, qApp, &aboutToQuit);
0102     QObject::connect(statusNotifier, &StatusNotifier::activated, qApp, [] {
0103         openDrKonqiDialog(DrKonqiDialog::GoTo::Main);
0104     });
0105     QObject::connect(statusNotifier, &StatusNotifier::sentryActivated, qApp, [] {
0106         qDebug() << "Sending report to sentry automatically";
0107         openDrKonqiDialog(DrKonqiDialog::GoTo::Sentry);
0108     });
0109 }
0110 
0111 bool isShuttingDown()
0112 {
0113     if (QDBusConnection::sessionBus().isConnected()) {
0114         QDBusInterface remoteApp(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"));
0115 
0116         QDBusReply<bool> reply = remoteApp.call(QStringLiteral("isShuttingDown"));
0117         return reply.isValid() ? reply.value() : false;
0118     }
0119     return false;
0120 }
0121 }
0122 
0123 int main(int argc, char *argv[])
0124 {
0125 #ifndef Q_OS_WIN // krazy:exclude=cpp
0126     // Drop privs.
0127     setgid(getgid());
0128     if (setuid(getuid()) < 0 && geteuid() != getuid()) {
0129         exit(255);
0130     }
0131 #endif
0132 
0133     QApplication app(argc, argv);
0134     KLocalizedString::setApplicationDomain(QByteArrayLiteral("drkonqi"));
0135 
0136     // We have somewhat special close behavior because of the internal debugger instance as well as auto-submission.
0137     // Make sure to honor SIGINT in a timely manner.
0138     KSignalHandler::self()->watchSignal(SIGINT);
0139     QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &app, [&app](int signal) {
0140         if (signal == SIGINT) {
0141             app.quit();
0142         }
0143     });
0144 
0145     // Prevent KApplication from setting the crash handler. We will set it later...
0146     setenv("KDE_DEBUG", "true", 1);
0147     // Session management is not needed, do not even connect in order to survive longer than ksmserver.
0148     unsetenv("SESSION_MANAGER");
0149 
0150     KAboutData aboutData(QStringLiteral("drkonqi"),
0151                          i18n("Crash Handler"),
0152                          QString::fromLatin1(version),
0153                          i18n("Crash Handler gives the user feedback "
0154                               "if a program has crashed."),
0155                          KAboutLicense::GPL,
0156                          i18n("(C) 2000-2018, The DrKonqi Authors"));
0157     aboutData.addAuthor(i18nc("@info:credit", "Hans Petter Bieker"), QString(), QStringLiteral("bieker@kde.org"));
0158     aboutData.addAuthor(i18nc("@info:credit", "Dario Andres Rodriguez"), QString(), QStringLiteral("andresbajotierra@gmail.com"));
0159     aboutData.addAuthor(i18nc("@info:credit", "George Kiagiadakis"), QString(), QStringLiteral("gkiagia@users.sourceforge.net"));
0160     aboutData.addAuthor(i18nc("@info:credit", "A. L. Spehr"), QString(), QStringLiteral("spehr@kde.org"));
0161     KAboutData::setApplicationData(aboutData);
0162     app.setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"), app.windowIcon()));
0163 
0164     QCommandLineParser parser;
0165     aboutData.setupCommandLine(&parser);
0166 
0167     const QCommandLineOption signalOption(QStringLiteral("signal"), i18nc("@info:shell", "The signal <number> that was caught"), QStringLiteral("number"));
0168     const QCommandLineOption appNameOption(QStringLiteral("appname"), i18nc("@info:shell", "<Name> of the program"), QStringLiteral("name"));
0169     const QCommandLineOption appPathOption(QStringLiteral("apppath"), i18nc("@info:shell", "<Path> to the executable"), QStringLiteral("path"));
0170     const QCommandLineOption appVersionOption(QStringLiteral("appversion"), i18nc("@info:shell", "The <version> of the program"), QStringLiteral("version"));
0171     const QCommandLineOption bugAddressOption(QStringLiteral("bugaddress"), i18nc("@info:shell", "The bug <address> to use"), QStringLiteral("address"));
0172     const QCommandLineOption programNameOption(QStringLiteral("programname"), i18nc("@info:shell", "Translated <name> of the program"), QStringLiteral("name"));
0173     const QCommandLineOption productNameOption(QStringLiteral("productname"), i18nc("@info:shell", "Bugzilla product name"), QStringLiteral("name"));
0174     const QCommandLineOption pidOption(QStringLiteral("pid"), i18nc("@info:shell", "The <PID> of the program"), QStringLiteral("pid"));
0175     const QCommandLineOption startupIdOption(QStringLiteral("startupid"), i18nc("@info:shell", "Startup <ID> of the program"), QStringLiteral("id"));
0176     const QCommandLineOption kdeinitOption(QStringLiteral("kdeinit"), i18nc("@info:shell", "The program was started by kdeinit"));
0177     const QCommandLineOption saferOption(QStringLiteral("safer"), i18nc("@info:shell", "Disable arbitrary disk access"));
0178     const QCommandLineOption restartedOption(QStringLiteral("restarted"), i18nc("@info:shell", "The program has already been restarted"));
0179     const QCommandLineOption keepRunningOption(QStringLiteral("keeprunning"),
0180                                                i18nc("@info:shell",
0181                                                      "Keep the program running and generate "
0182                                                      "the backtrace at startup"));
0183     const QCommandLineOption threadOption(QStringLiteral("thread"), i18nc("@info:shell", "The <thread id> of the failing thread"), QStringLiteral("threadid"));
0184     const QCommandLineOption dialogOption(QStringLiteral("dialog"), i18nc("@info:shell", "Do not show a notification but launch the debug dialog directly"));
0185     const QCommandLineOption glRendererOption(u"glrenderer"_s, u"The GL_RENDERER used by the process"_s, u"glrenderer"_s);
0186     const QCommandLineOption exceptionNameOption(u"exceptionname"_s, u"The exception class name if an exception was the cause"_s, u"name"_s);
0187     const QCommandLineOption exceptionWhatOption(u"exceptionwhat"_s, u"The exception what string if an exception was the cause"_s, u"what"_s);
0188 
0189     parser.addOptions({signalOption,
0190                        appNameOption,
0191                        appPathOption,
0192                        appVersionOption,
0193                        bugAddressOption,
0194                        programNameOption,
0195                        productNameOption,
0196                        pidOption,
0197                        startupIdOption,
0198                        kdeinitOption,
0199                        saferOption,
0200                        restartedOption,
0201                        keepRunningOption,
0202                        threadOption,
0203                        dialogOption,
0204                        glRendererOption,
0205                        exceptionNameOption,
0206                        exceptionWhatOption});
0207 
0208     // Add all unknown options but make sure to print a warning.
0209     // This enables older DrKonqi's to run by newer KCrash instances with
0210     // possibly different/new options.
0211     // KCrash can always send all options it knows to send and be sure that
0212     // DrKonqi will not explode on them. If an option is not known here it's
0213     // either too old or too new.
0214     //
0215     // To implement this smartly we'll ::parse all arguments, and then ::process
0216     // them again once we have injected no-op options for all unknown ones.
0217     // This allows ::process to still do common argument handling for --version
0218     // as well as standard error handling.
0219     if (!parser.parse(app.arguments())) {
0220         const QStringList unknownOptionNames = parser.unknownOptionNames();
0221         for (const QString &option : unknownOptionNames) {
0222             qWarning() << "Unknown option" << option << " - ignoring it.";
0223             parser.addOption(QCommandLineOption(option));
0224         }
0225     }
0226 
0227     parser.process(app);
0228     aboutData.processCommandLine(&parser);
0229 
0230     DrKonqi::setSignal(parser.value(signalOption).toInt());
0231     DrKonqi::setAppName(parser.value(appNameOption));
0232     DrKonqi::setAppPath(parser.value(appPathOption));
0233     DrKonqi::setAppVersion(parser.value(appVersionOption));
0234     DrKonqi::setBugAddress(parser.value(bugAddressOption));
0235     DrKonqi::setProgramName(parser.value(programNameOption));
0236     DrKonqi::setProductName(parser.value(productNameOption));
0237     DrKonqi::setPid(parser.value(pidOption).toInt());
0238     DrKonqi::setKdeinit(parser.isSet(kdeinitOption));
0239     DrKonqi::setSafer(parser.isSet(saferOption));
0240     DrKonqi::setRestarted(parser.isSet(restartedOption));
0241     DrKonqi::setKeepRunning(parser.isSet(keepRunningOption));
0242     DrKonqi::setThread(parser.value(threadOption).toInt());
0243     DrKonqi::setStartupId(parser.value(startupIdOption));
0244     DrKonqi::instance()->m_glRenderer = parser.value(glRendererOption);
0245     DrKonqi::instance()->m_exceptionName = parser.value(exceptionNameOption);
0246     if (!DrKonqi::instance()->m_exceptionName.isEmpty()) {
0247         QProcess proc;
0248         proc.setProgram(u"c++filt"_s);
0249         proc.setArguments({u"--types"_s, DrKonqi::instance()->m_exceptionName});
0250         proc.start();
0251         if (proc.waitForFinished((1000ms).count())) {
0252             DrKonqi::instance()->m_exceptionName = QString::fromUtf8(proc.readAllStandardOutput().trimmed());
0253         }
0254         if (DrKonqi::instance()->m_exceptionName.isEmpty()) { // restore the potentially mangled version if empty
0255             DrKonqi::instance()->m_exceptionName = parser.value(exceptionNameOption);
0256         }
0257     }
0258     DrKonqi::instance()->m_exceptionWhat = parser.value(exceptionWhatOption);
0259     auto forceDialog = parser.isSet(dialogOption);
0260 
0261     if (!DrKonqi::init()) {
0262         return 1;
0263     }
0264 
0265     app.setQuitOnLastWindowClosed(false);
0266     // https://bugs.kde.org/show_bug.cgi?id=471941
0267     app.setQuitLockEnabled(false);
0268 
0269     const bool restarted = parser.isSet(restartedOption);
0270 
0271     // Whether the user should be encouraged to file a bug report
0272     const bool interactionAllowed = KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("General")).readEntry("InteractionAllowed", true);
0273     const bool shuttingDown = isShuttingDown();
0274 
0275     if (forceDialog) {
0276         openDrKonqiDialog();
0277     } else if (shuttingDown) {
0278         return 0;
0279     } else {
0280         // if no notification service is running (eg. shell crashed, or other desktop environment)
0281         // and we didn't auto-restart the app, open DrKonqi dialog instead of showing an SNI
0282         // and emitting a desktop notification.
0283         if (!StatusNotifier::notificationServiceRegistered() && !restarted) {
0284             openDrKonqiDialog();
0285         } else { // StatusNotifierItem (interaction) or notification (no interaction)
0286             requestDrKonqiDialog(restarted, interactionAllowed);
0287         }
0288     }
0289 
0290     return app.exec();
0291 }