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