File indexing completed on 2024-12-08 07:56:30
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 "coredumpwatcher.h" 0007 0008 #include <cerrno> 0009 #include <optional> 0010 #include <utility> 0011 0012 #include <sys/resource.h> 0013 #include <sys/un.h> 0014 #include <unistd.h> 0015 0016 #include "coredump.h" 0017 #include "socket.h" 0018 0019 using namespace Qt::StringLiterals; 0020 0021 static std::optional<Coredump> makeDump(sd_journal *context) 0022 { 0023 auto cursorExpected = contextual_owning_ptr_call<char>(sd_journal_get_cursor, context, std::free); 0024 if (cursorExpected.ret != 0) { 0025 qFatal("Failed to get entry cursor"); 0026 return std::nullopt; 0027 } 0028 0029 Coredump::EntriesHash entries; 0030 const void *data = nullptr; 0031 size_t length = 0; 0032 SD_JOURNAL_FOREACH_DATA(context, data, length) 0033 { 0034 // size_t is uint, QBA uses int, make sure we don't overflow the int! 0035 int dataSize = static_cast<int>(length); 0036 Q_ASSERT(dataSize >= 0); 0037 Q_ASSERT(static_cast<quint64>(dataSize) == length); 0038 0039 QByteArray entry(static_cast<const char *>(data), dataSize); 0040 const auto offset = entry.indexOf('='); 0041 if (offset < 0) { 0042 qWarning() << "this entry looks funny it has no separating = character" << entry; 0043 continue; 0044 } 0045 0046 const QByteArray key = entry.left(offset); 0047 if (key == QByteArrayLiteral("COREDUMP")) { 0048 // The literal COREDUMP= entry is the actual core when configured for journal storage in coredump.conf. 0049 // Synthesize a filename instead so we can use the same validity checks for all storage types. 0050 entries.insert(Coredump::keyFilename(), QByteArrayLiteral("/dev/null")); 0051 continue; 0052 } 0053 0054 const QByteArray value = entry.mid(offset + 1); 0055 0056 // Always add to raw data, they get serialized back into the INI file for drkonqi. 0057 entries.insert(key, value); 0058 } 0059 0060 return std::make_optional<Coredump>(cursorExpected.value.get(), entries); 0061 } 0062 0063 CoredumpWatcher::CoredumpWatcher(std::unique_ptr<sd_journal> context_, QString bootId_, const QString &instance_, QObject *parent) 0064 : QObject(parent) 0065 , context(std::move(context_)) 0066 , bootId(std::move(bootId_)) 0067 , instance(instance_) 0068 , instanceFilter(QStringLiteral("systemd-coredump@%1").arg(instance_)) 0069 { 0070 } 0071 0072 void CoredumpWatcher::processLog() 0073 { 0074 int i = 0; 0075 while (sd_journal_next(context.get()) > 0) { 0076 ++i; 0077 const auto optionalDump = makeDump(context.get()); 0078 if (!optionalDump.has_value()) { 0079 qWarning() << "Failed to make a dump :O"; 0080 continue; 0081 } 0082 0083 const Coredump &dump = optionalDump.value(); 0084 if (!dump.systemd_unit.startsWith(instanceFilter)) { 0085 // Older systemds have trouble templating a correct instance. We only 0086 // perform a startsWith check here, but will filter more aggressively 0087 // whenever possible via the constructor. 0088 continue; 0089 } 0090 if (dump.exe.isEmpty() && dump.filename.isEmpty()) { 0091 qDebug() << "Entry doesn't look like a dump. This may have been a vaccum run. Nothing to process."; 0092 // Do not finish here. Vaccum log entires are created from real coredump processes. We should eventually 0093 // find a dump. 0094 continue; 0095 } 0096 0097 qDebug() << dump.exe << dump.pid << dump.filename; 0098 0099 Q_EMIT newDump(dump); 0100 0101 constexpr int maximumInBatch = 128; // give the event loop a chance to do other stuff as well 0102 if (i >= maximumInBatch) { 0103 // reschedule run 0104 QMetaObject::invokeMethod(this, &CoredumpWatcher::processLog, Qt::QueuedConnection); 0105 return; 0106 } 0107 } 0108 Q_EMIT atLogEnd(); 0109 } 0110 0111 void CoredumpWatcher::errnoError(const QString &msg, int err) 0112 { 0113 Q_EMIT error(msg + QStringLiteral(": (%1) ").arg(QString::number(err)) + QString::fromLocal8Bit(strerror(err))); 0114 } 0115 0116 void CoredumpWatcher::start() 0117 { 0118 Q_ASSERT(context); 0119 0120 sd_journal_flush_matches(context.get()); // reset match 0121 if (sd_journal_add_match(context.get(), "SYSLOG_IDENTIFIER=systemd-coredump", 0) != 0) { 0122 Q_EMIT error(QStringLiteral("Failed to install id match")); 0123 return; 0124 } 0125 0126 if (!bootId.isEmpty()) { 0127 const QString bootIdMatch = QStringLiteral("_BOOT_ID=%1").arg(bootId); 0128 if (sd_journal_add_match(context.get(), qPrintable(bootIdMatch), 0) != 0) { 0129 Q_EMIT error(QStringLiteral("Failed to install boot id match")); 0130 return; 0131 } 0132 } 0133 0134 if (!instance.isEmpty()) { 0135 if (instance.count(QLatin1Char('-')) >= 2) { 0136 // older systemds have a bug where %I doesn't actually expand correctly and only contains the first element. 0137 // This prevents us from matching through sd API. Instead processLog will filter based on the instance 0138 // information prefix. It's still unique enough. 0139 // Auto-generated instance names are of the form 0140 // $iid-$pid-$uid where iid is a growing instance id number. 0141 // Additionally we'll filter by chrono proximity, iids that are too far in the past will be discarded. 0142 // This is because iid on its own isn't necessarily unique in the event that it wraps around whatever 0143 // integer limit it has. 0144 if (sd_journal_add_match(context.get(), qPrintable(QStringLiteral("_SYSTEMD_UNIT=%1.service").arg(instanceFilter)), 0) != 0) { 0145 Q_EMIT error(QStringLiteral("Failed to install unit match")); 0146 return; 0147 } 0148 } 0149 } 0150 0151 for (const auto &match : matches) { 0152 if (sd_journal_add_match(context.get(), qUtf8Printable(match), 0) != 0) { 0153 Q_EMIT error(u"Failed to install custom match: %1"_s.arg(match)); 0154 return; 0155 } 0156 } 0157 0158 const int fd = sd_journal_get_fd(context.get()); 0159 if (fd < 0) { 0160 errnoError(QStringLiteral("Failed to get listening socket"), -fd); 0161 return; 0162 } 0163 0164 notifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read); 0165 connect(notifier.get(), &QSocketNotifier::activated, this, [this] { 0166 if (sd_journal_process(context.get()) != SD_JOURNAL_APPEND) { 0167 return; 0168 } 0169 processLog(); 0170 }); 0171 0172 if (int ret = sd_journal_seek_head(context.get()); ret != 0) { 0173 errnoError(QStringLiteral("Failed to go to tail"), -fd); 0174 return; 0175 } 0176 // Make sure to read whatever we have pending on next loop. 0177 QMetaObject::invokeMethod(this, &CoredumpWatcher::processLog); 0178 } 0179 0180 void CoredumpWatcher::addMatch(const QString &str) 0181 { 0182 matches.push_back(str); 0183 } 0184 0185 #include "moc_coredumpwatcher.cpp"