File indexing completed on 2024-04-28 05:26:50
0001 /* 0002 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0003 SPDX-FileCopyrightText: 2019-2022 Harald Sitter <sitter@kde.org> 0004 */ 0005 0006 #include <QCommandLineParser> 0007 #include <QCoreApplication> 0008 #include <QDebug> 0009 #include <QFile> 0010 #include <QJsonDocument> 0011 #include <QLocalSocket> 0012 #include <QScopeGuard> 0013 0014 #include <sys/resource.h> 0015 #include <sys/socket.h> 0016 #include <sys/un.h> 0017 #include <unistd.h> 0018 0019 #include <coredump.h> 0020 #include <coredumpwatcher.h> 0021 #include <socket.h> 0022 0023 using namespace Qt::StringLiterals; 0024 0025 int main(int argc, char **argv) 0026 { 0027 QCoreApplication app(argc, argv); 0028 app.setApplicationName(QStringLiteral("drkonqi-coredump-helper")); 0029 app.setOrganizationDomain(QStringLiteral("kde.org")); 0030 0031 // This binary is for internal use and intentionally has no i18n! 0032 QCommandLineParser parser; 0033 parser.addHelpOption(); 0034 parser.addVersionOption(); 0035 QCommandLineOption pickupOption("pickup"_L1, "Picking up old crashes, don't handle them if you can't tell if they were handled."_L1); 0036 parser.addOption(pickupOption); 0037 QCommandLineOption uidOption("uid"_L1, "UID to filter"_L1, "uid"_L1); 0038 parser.addOption(uidOption); 0039 QCommandLineOption bootIdOption("boot-id"_L1, "systemd boot id to filter"_L1, "bootId"_L1); 0040 parser.addOption(bootIdOption); 0041 QCommandLineOption instanceOption("instance"_L1, "systemd-coredump@.service instance to filter"_L1, "instance"_L1); 0042 parser.addOption(instanceOption); 0043 parser.process(app); 0044 const bool pickup = parser.isSet(pickupOption); 0045 const QString uid = parser.value(uidOption); 0046 const QString bootId = parser.value(bootIdOption); 0047 const QString instance = parser.value(instanceOption); 0048 0049 auto expectedJournal = owning_ptr_call<sd_journal>(sd_journal_open, SD_JOURNAL_LOCAL_ONLY); 0050 Q_ASSERT(expectedJournal.ret == 0); 0051 Q_ASSERT(expectedJournal.value); 0052 0053 CoredumpWatcher watcher(std::move(expectedJournal.value), bootId, instance, nullptr); 0054 if (!uid.isEmpty()) { 0055 watcher.addMatch(u"COREDUMP_UID=%1"_s.arg(uid)); 0056 } 0057 QObject::connect(&watcher, &CoredumpWatcher::newDump, &app, [&watcher, pickup](const Coredump &dump) { 0058 if (pickup && !QFile::exists(dump.filename)) { 0059 // We only ignore missing cores when picking up old crashes. When dealing with new ones we may still wish 0060 // to notify that something has crashed, even when we can't debug it. 0061 return; 0062 } 0063 0064 // We only try to find the socket file at this point in time because we need to know the UID and on older systemd's we'll not 0065 // be able to figure this out from just the instance information. 0066 // When systemd 245 (Ubuntu 20.04) no longer is out in the wild we can move this into the main and get the 0067 // uid from the instance. 0068 // TODO: move this to main scope as per the comment above 0069 const QByteArray socketPath = QByteArrayLiteral("/run/user/") + QByteArray::number(dump.uid) + QByteArrayLiteral("/drkonqi-coredump-launcher"); 0070 if (!QFile::exists(QString::fromUtf8(socketPath))) { 0071 // This is intentionally not an error or fatal, not all users necessarily have 0072 // a socket or drkonqi! 0073 qWarning() << "The socket path doesn't exist @" << socketPath; 0074 Q_EMIT watcher.finished(); 0075 return; 0076 } 0077 0078 sockaddr_un sa{}; 0079 sa.sun_family = AF_UNIX; 0080 // size_t is signed, ensure path is too 0081 Q_ASSERT(socketPath.size() >= 0); 0082 const std::make_unsigned<decltype(socketPath.size())>::type pathSize = socketPath.size(); 0083 if (pathSize > sizeof(sa.sun_path) /* '>' because we need an extra byte for null */) { 0084 Q_EMIT watcher.error(QStringLiteral("The socket path has too many characters:") + QString::fromLatin1(socketPath)); 0085 return; 0086 } 0087 strncpy(static_cast<char *>(sa.sun_path), socketPath.constData(), sizeof(sa.sun_path)); 0088 0089 const int fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); 0090 if (fd < 0) { 0091 Q_EMIT watcher.error(QString::fromLocal8Bit(strerror(errno))); 0092 Q_UNREACHABLE(); 0093 } 0094 QScopeGuard closeFD([fd] { 0095 close(fd); 0096 }); 0097 0098 if (::connect(fd, (sockaddr *)&sa, sizeof(sa)) < 0) { // NOLINT 0099 Q_EMIT watcher.error(QString::fromLocal8Bit(strerror(errno))); 0100 return; 0101 } 0102 0103 // Convert the raw data to JSON and send that over the socket. This means 0104 // the client side doesn't need to talk to journald again. A tad more efficient, 0105 // and it makes nary a difference in code. 0106 QVariantMap variantMap; 0107 if (pickup) { // forward this into the launcher so it can choose to not have dump trucks handle dumps without metadata 0108 variantMap.insert(QString::fromUtf8(Coredump::keyPickup()), "TRUE"_ba); 0109 } 0110 for (auto it = dump.m_rawData.cbegin(); it != dump.m_rawData.cend(); ++it) { 0111 variantMap.insert(QString::fromUtf8(it.key()), it.value()); 0112 } 0113 0114 QLocalSocket s; 0115 s.setSocketDescriptor(fd, QLocalSocket::ConnectedState, QLocalSocket::WriteOnly); 0116 QByteArray data = QJsonDocument::fromVariant(variantMap).toJson(); 0117 while (!data.isEmpty()) { 0118 // NB: we need to constrain the segment size to not run into QLocalSocket::DatagramTooLargeError 0119 const qint64 written = s.write(data.constData(), std::min<int>(data.size(), Socket::DatagramSize)); 0120 if (written > 0) { 0121 data = data.mid(written); 0122 s.waitForBytesWritten(); 0123 } else if (s.state() != QLocalSocket::ConnectedState) { 0124 qWarning() << "socket state unexpectedly" << s.state() << "aborting crash processing"; 0125 qApp->quit(); 0126 return; 0127 } 0128 } 0129 s.flush(); 0130 s.waitForBytesWritten(); 0131 0132 Q_EMIT watcher.finished(); 0133 return; 0134 }); 0135 0136 QObject::connect(&watcher, &CoredumpWatcher::finished, &app, &QCoreApplication::quit, Qt::QueuedConnection); 0137 QObject::connect( 0138 &watcher, 0139 &CoredumpWatcher::error, 0140 &app, 0141 [](const QString &msg) { 0142 qWarning() << msg; 0143 qApp->exit(1); 0144 }, 0145 Qt::QueuedConnection); 0146 watcher.start(); 0147 0148 return app.exec(); 0149 }