File indexing completed on 2023-12-03 05:36:25
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 }