File indexing completed on 2024-03-24 17:01:27

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 <cstdlib>
0011 #include <unistd.h>
0012 
0013 #include <QIcon>
0014 
0015 #include <KAboutData>
0016 #include <KConfigGroup>
0017 #include <KLocalizedString>
0018 #include <KSharedConfig>
0019 #include <QCommandLineParser>
0020 #include <QDebug>
0021 #include <QFile>
0022 #include <QTemporaryFile>
0023 
0024 #include <QDBusConnection>
0025 #include <QDBusInterface>
0026 #include <QDBusReply>
0027 
0028 #include <config-drkonqi.h>
0029 
0030 #ifdef Q_OS_MACOS
0031 #include <KWindowSystem>
0032 #endif
0033 
0034 #include "backtracegenerator.h"
0035 #include "config-drkonqi.h"
0036 #include "debuggermanager.h"
0037 #include "drkonqi.h"
0038 #include "drkonqidialog.h"
0039 #include "statusnotifier.h"
0040 
0041 static const char version[] = PROJECT_VERSION;
0042 
0043 namespace
0044 {
0045 // Clean on-disk data before quitting. This ought to only happen if we are
0046 // certain that the user doesn't want the crash to appear again.
0047 void cleanupAfterUserQuit()
0048 {
0049     DrKonqi::cleanupBeforeQuit();
0050     qApp->quit();
0051 }
0052 
0053 void openDrKonqiDialog()
0054 {
0055     auto *w = new DrKonqiDialog();
0056     QObject::connect(qApp, &QCoreApplication::aboutToQuit, w, &QObject::deleteLater);
0057     QObject::connect(w, &DrKonqiDialog::rejected, qApp, &cleanupAfterUserQuit);
0058     w->show();
0059 #ifdef Q_OS_MACOS
0060     KWindowSystem::forceActiveWindow(w->winId());
0061 #endif
0062 }
0063 
0064 void requestDrKonqiDialog(bool restarted, bool interactionAllowed)
0065 {
0066     auto *statusNotifier = new StatusNotifier();
0067     statusNotifier->setActivationAllowed(interactionAllowed);
0068     if (interactionAllowed) {
0069         statusNotifier->show();
0070     }
0071     if (!restarted) {
0072         statusNotifier->notify();
0073     }
0074     QObject::connect(statusNotifier, &StatusNotifier::expired, qApp, &cleanupAfterUserQuit);
0075     QObject::connect(statusNotifier, &StatusNotifier::activated, &openDrKonqiDialog);
0076 }
0077 
0078 bool isShuttingDown()
0079 {
0080     if (QDBusConnection::sessionBus().isConnected()) {
0081         QDBusInterface remoteApp(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"));
0082 
0083         QDBusReply<bool> reply = remoteApp.call(QStringLiteral("isShuttingDown"));
0084         return reply.isValid() ? reply.value() : false;
0085     }
0086     return false;
0087 }
0088 }
0089 
0090 int main(int argc, char *argv[])
0091 {
0092 #ifndef Q_OS_WIN // krazy:exclude=cpp
0093     // Drop privs.
0094     setgid(getgid());
0095     if (setuid(getuid()) < 0 && geteuid() != getuid()) {
0096         exit(255);
0097     }
0098 #endif
0099 
0100     QApplication app(argc, argv);
0101 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0102     app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
0103 #endif
0104     KLocalizedString::setApplicationDomain("drkonqi5");
0105 
0106     // Prevent KApplication from setting the crash handler. We will set it later...
0107     setenv("KDE_DEBUG", "true", 1);
0108     // Session management is not needed, do not even connect in order to survive longer than ksmserver.
0109     unsetenv("SESSION_MANAGER");
0110 
0111     KAboutData aboutData(QStringLiteral("drkonqi"),
0112                          i18n("The KDE Crash Handler"),
0113                          QString::fromLatin1(version),
0114                          i18n("The KDE Crash Handler gives the user feedback "
0115                               "if a program has crashed."),
0116                          KAboutLicense::GPL,
0117                          i18n("(C) 2000-2018, The DrKonqi Authors"));
0118     aboutData.addAuthor(i18nc("@info:credit", "Hans Petter Bieker"), QString(), QStringLiteral("bieker@kde.org"));
0119     aboutData.addAuthor(i18nc("@info:credit", "Dario Andres Rodriguez"), QString(), QStringLiteral("andresbajotierra@gmail.com"));
0120     aboutData.addAuthor(i18nc("@info:credit", "George Kiagiadakis"), QString(), QStringLiteral("gkiagia@users.sourceforge.net"));
0121     aboutData.addAuthor(i18nc("@info:credit", "A. L. Spehr"), QString(), QStringLiteral("spehr@kde.org"));
0122     KAboutData::setApplicationData(aboutData);
0123     app.setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"), app.windowIcon()));
0124 
0125     QCommandLineParser parser;
0126     aboutData.setupCommandLine(&parser);
0127 
0128     const QCommandLineOption signalOption(QStringLiteral("signal"), i18nc("@info:shell", "The signal <number> that was caught"), QStringLiteral("number"));
0129     const QCommandLineOption appNameOption(QStringLiteral("appname"), i18nc("@info:shell", "<Name> of the program"), QStringLiteral("name"));
0130     const QCommandLineOption appPathOption(QStringLiteral("apppath"), i18nc("@info:shell", "<Path> to the executable"), QStringLiteral("path"));
0131     const QCommandLineOption appVersionOption(QStringLiteral("appversion"), i18nc("@info:shell", "The <version> of the program"), QStringLiteral("version"));
0132     const QCommandLineOption bugAddressOption(QStringLiteral("bugaddress"), i18nc("@info:shell", "The bug <address> to use"), QStringLiteral("address"));
0133     const QCommandLineOption programNameOption(QStringLiteral("programname"), i18nc("@info:shell", "Translated <name> of the program"), QStringLiteral("name"));
0134     const QCommandLineOption productNameOption(QStringLiteral("productname"), i18nc("@info:shell", "Bugzilla product name"), QStringLiteral("name"));
0135     const QCommandLineOption pidOption(QStringLiteral("pid"), i18nc("@info:shell", "The <PID> of the program"), QStringLiteral("pid"));
0136     const QCommandLineOption startupIdOption(QStringLiteral("startupid"), i18nc("@info:shell", "Startup <ID> of the program"), QStringLiteral("id"));
0137     const QCommandLineOption kdeinitOption(QStringLiteral("kdeinit"), i18nc("@info:shell", "The program was started by kdeinit"));
0138     const QCommandLineOption saferOption(QStringLiteral("safer"), i18nc("@info:shell", "Disable arbitrary disk access"));
0139     const QCommandLineOption restartedOption(QStringLiteral("restarted"), i18nc("@info:shell", "The program has already been restarted"));
0140     const QCommandLineOption keepRunningOption(QStringLiteral("keeprunning"),
0141                                                i18nc("@info:shell",
0142                                                      "Keep the program running and generate "
0143                                                      "the backtrace at startup"));
0144     const QCommandLineOption threadOption(QStringLiteral("thread"), i18nc("@info:shell", "The <thread id> of the failing thread"), QStringLiteral("threadid"));
0145     const QCommandLineOption dialogOption(QStringLiteral("dialog"), i18nc("@info:shell", "Do not show a notification but launch the debug dialog directly"));
0146 
0147     parser.addOptions({signalOption,
0148                        appNameOption,
0149                        appPathOption,
0150                        appVersionOption,
0151                        bugAddressOption,
0152                        programNameOption,
0153                        productNameOption,
0154                        pidOption,
0155                        startupIdOption,
0156                        kdeinitOption,
0157                        saferOption,
0158                        restartedOption,
0159                        keepRunningOption,
0160                        threadOption,
0161                        dialogOption});
0162 
0163     // Add all unknown options but make sure to print a warning.
0164     // This enables older DrKonqi's to run by newer KCrash instances with
0165     // possibly different/new options.
0166     // KCrash can always send all options it knows to send and be sure that
0167     // DrKonqi will not explode on them. If an option is not known here it's
0168     // either too old or too new.
0169     //
0170     // To implement this smartly we'll ::parse all arguments, and then ::process
0171     // them again once we have injected no-op options for all unknown ones.
0172     // This allows ::process to still do common argument handling for --version
0173     // as well as standard error handling.
0174     if (!parser.parse(app.arguments())) {
0175         const QStringList unknownOptionNames = parser.unknownOptionNames();
0176         for (const QString &option : unknownOptionNames) {
0177             qWarning() << "Unknown option" << option << " - ignoring it.";
0178             parser.addOption(QCommandLineOption(option));
0179         }
0180     }
0181 
0182     parser.process(app);
0183     aboutData.processCommandLine(&parser);
0184 
0185     DrKonqi::setSignal(parser.value(signalOption).toInt());
0186     DrKonqi::setAppName(parser.value(appNameOption));
0187     DrKonqi::setAppPath(parser.value(appPathOption));
0188     DrKonqi::setAppVersion(parser.value(appVersionOption));
0189     DrKonqi::setBugAddress(parser.value(bugAddressOption));
0190     DrKonqi::setProgramName(parser.value(programNameOption));
0191     DrKonqi::setProductName(parser.value(productNameOption));
0192     DrKonqi::setPid(parser.value(pidOption).toInt());
0193     DrKonqi::setKdeinit(parser.isSet(kdeinitOption));
0194     DrKonqi::setSafer(parser.isSet(saferOption));
0195     DrKonqi::setRestarted(parser.isSet(restartedOption));
0196     DrKonqi::setKeepRunning(parser.isSet(keepRunningOption));
0197     DrKonqi::setThread(parser.value(threadOption).toInt());
0198     DrKonqi::setStartupId(parser.value(startupIdOption));
0199     auto forceDialog = parser.isSet(dialogOption);
0200 
0201     if (!DrKonqi::init()) {
0202         return 1;
0203     }
0204 
0205     app.setQuitOnLastWindowClosed(false);
0206 
0207     const bool restarted = parser.isSet(restartedOption);
0208 
0209     // Whether the user should be encouraged to file a bug report
0210     const bool interactionAllowed = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("InteractionAllowed", true);
0211     const bool shuttingDown = isShuttingDown();
0212 
0213     // For automatically restarted services or during shutdown, do nothing in case no interaction is allowed
0214     if (!forceDialog && !interactionAllowed && (restarted || shuttingDown)) {
0215         return 0;
0216     }
0217 
0218     // if no notification service is running (eg. shell crashed, or other desktop environment)
0219     // and we didn't auto-restart the app, open DrKonqi dialog instead of showing an SNI
0220     // and emitting a desktop notification.
0221     if (shuttingDown) {
0222         DrKonqi::shutdownSaveReport();
0223     } else if (forceDialog || (!restarted && !StatusNotifier::notificationServiceRegistered())) {
0224         openDrKonqiDialog();
0225     } else {
0226         requestDrKonqiDialog(restarted, interactionAllowed);
0227     }
0228 
0229     return app.exec();
0230 }