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 }