File indexing completed on 2024-04-14 15:32:43

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 int main(int argc, char **argv)
0024 {
0025     QCoreApplication app(argc, argv);
0026     app.setApplicationName(QStringLiteral("drkonqi-coredump-helper"));
0027     app.setOrganizationDomain(QStringLiteral("kde.org"));
0028 
0029     // This binary is for internal use and intentionally has no i18n!
0030     QCommandLineParser parser;
0031     parser.addHelpOption();
0032     parser.addVersionOption();
0033     parser.addPositionalArgument(QStringLiteral("boot-id"), QStringLiteral("Boot ID"));
0034     parser.addPositionalArgument(QStringLiteral("instance"), QStringLiteral("Template instance (forwareded from systemd-coredump@)"));
0035     parser.process(app);
0036     const QStringList args = parser.positionalArguments();
0037     Q_ASSERT(args.size() == 2);
0038     const QString &bootId = args.at(0);
0039     const QString &instance = args.at(1);
0040 
0041     auto expectedJournal = owning_ptr_call<sd_journal>(sd_journal_open, SD_JOURNAL_LOCAL_ONLY);
0042     Q_ASSERT(expectedJournal.ret == 0);
0043     Q_ASSERT(expectedJournal.value);
0044 
0045     CoredumpWatcher watcher(std::move(expectedJournal.value), bootId, instance, nullptr);
0046     QObject::connect(&watcher, &CoredumpWatcher::newDump, &app, [&watcher](const Coredump &dump) {
0047         // We only try to find the socket file here because we need to know the UID and on older systemd's we'll not
0048         // be able to figure this out from just the instance information.
0049         // When systemd 245 (Ubuntu 20.04) no longer is out in the wild we can move this into the main and get the
0050         // uid from the instance.
0051         const QByteArray socketPath = QByteArrayLiteral("/run/user/") + QByteArray::number(dump.uid) + QByteArrayLiteral("/drkonqi-coredump-launcher");
0052         if (!QFile::exists(QString::fromUtf8(socketPath))) {
0053             // This is intentionally not an error or fatal, not all users necessarily have
0054             // a socket or drkonqi!
0055             qWarning() << "The socket path doesn't exist @" << socketPath;
0056             Q_EMIT watcher.finished();
0057             return;
0058         }
0059 
0060         sockaddr_un sa{};
0061         sa.sun_family = AF_UNIX;
0062         // size_t is signed, ensure path is too
0063         Q_ASSERT(socketPath.size() >= 0);
0064         const std::make_unsigned<decltype(socketPath.size())>::type pathSize = socketPath.size();
0065         if (pathSize > sizeof(sa.sun_path) /* '>' because we need an extra byte for null */) {
0066             Q_EMIT watcher.error(QStringLiteral("The socket path has too many characters:") + QString::fromLatin1(socketPath));
0067             return;
0068         }
0069         strncpy(static_cast<char *>(sa.sun_path), socketPath.constData(), sizeof(sa.sun_path));
0070 
0071         const int fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
0072         if (fd < 0) {
0073             Q_EMIT watcher.error(QString::fromLocal8Bit(strerror(errno)));
0074             Q_UNREACHABLE();
0075         }
0076         QScopeGuard closeFD([fd] {
0077             close(fd);
0078         });
0079 
0080         if (::connect(fd, (sockaddr *)&sa, sizeof(sa)) < 0) { // NOLINT
0081             Q_EMIT watcher.error(QString::fromLocal8Bit(strerror(errno)));
0082             return;
0083         }
0084 
0085         // Convert the raw data to JSON and send that over the socket. This means
0086         // the client side doesn't need to talk to journald again. A tad more efficient,
0087         // and it makes nary a difference in code.
0088         QVariantMap variantMap;
0089         for (auto it = dump.m_rawData.cbegin(); it != dump.m_rawData.cend(); ++it) {
0090             variantMap.insert(QString::fromUtf8(it.key()), it.value());
0091         }
0092 
0093         QLocalSocket s;
0094         s.setSocketDescriptor(fd, QLocalSocket::ConnectedState, QLocalSocket::WriteOnly);
0095         QByteArray data = QJsonDocument::fromVariant(variantMap).toJson();
0096         while (!data.isEmpty()) {
0097             // NB: we need to constrain the segment size to not run into QLocalSocket::DatagramTooLargeError
0098             const qint64 written = s.write(data.constData(), std::min<int>(data.size(), Socket::DatagramSize));
0099             if (written > 0) {
0100                 data = data.mid(written);
0101                 s.waitForBytesWritten();
0102             }
0103         }
0104         s.flush();
0105         s.waitForBytesWritten();
0106 
0107         Q_EMIT watcher.finished();
0108         return;
0109     });
0110 
0111     QObject::connect(&watcher, &CoredumpWatcher::finished, &app, &QCoreApplication::quit, Qt::QueuedConnection);
0112     QObject::connect(
0113         &watcher,
0114         &CoredumpWatcher::error,
0115         &app,
0116         [](const QString &msg) {
0117             qWarning() << msg;
0118             qApp->exit(1);
0119         },
0120         Qt::QueuedConnection);
0121     watcher.start();
0122 
0123     return app.exec();
0124 }