File indexing completed on 2024-05-19 05:49:16

0001 /*
0002     SPDX-FileCopyrightText: 2007 Nicolas Ternisien <nicolas.ternisien@gmail.com>
0003     SPDX-FileCopyrightText: 2015 Vyacheslav Matyushin
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "journaldLocalAnalyzer.h"
0009 #include "journaldConfiguration.h"
0010 #include "ksystemlogConfig.h"
0011 #include "ksystemlog_debug.h"
0012 #include "logViewModel.h"
0013 
0014 #include <KLocalizedString>
0015 
0016 #include <QtConcurrent>
0017 
0018 JournaldLocalAnalyzer::JournaldLocalAnalyzer(LogMode *mode, QString filter)
0019     : JournaldAnalyzer(mode)
0020 {
0021     // Initialize journal access flags and open the journal.
0022     mJournalFlags = 0;
0023     auto *configuration = mode->logModeConfiguration<JournaldConfiguration *>();
0024     switch (configuration->entriesType()) {
0025     case JournaldConfiguration::EntriesAll:
0026         break;
0027     case JournaldConfiguration::EntriesCurrentUser:
0028         mJournalFlags |= SD_JOURNAL_CURRENT_USER;
0029         break;
0030     case JournaldConfiguration::EntriesSystem:
0031         mJournalFlags |= SD_JOURNAL_SYSTEM;
0032         break;
0033     }
0034     const int ret = sd_journal_open(&mJournal, mJournalFlags);
0035     if (ret < 0) {
0036         qCWarning(KSYSTEMLOG) << "Journald analyzer failed to open system journal";
0037         return;
0038     }
0039 
0040     const qintptr fd = sd_journal_get_fd(mJournal);
0041     mJournalNotifier = new QSocketNotifier(fd, QSocketNotifier::Read);
0042     mJournalNotifier->setEnabled(false);
0043     connect(mJournalNotifier, &QSocketNotifier::activated, this, &JournaldLocalAnalyzer::journalDescriptorUpdated);
0044 
0045     if (configuration->displayCurrentBootOnly()) {
0046         QFile file(QLatin1String("/proc/sys/kernel/random/boot_id"));
0047         if (file.open(QIODevice::ReadOnly | QFile::Text)) {
0048             QTextStream stream(&file);
0049             mCurrentBootID = stream.readAll().trimmed();
0050             mCurrentBootID.remove(QChar::fromLatin1('-'));
0051             mFilters << QStringLiteral("_BOOT_ID=%1").arg(mCurrentBootID);
0052         } else {
0053             qCWarning(KSYSTEMLOG) << "Journald analyzer failed to open /proc/sys/kernel/random/boot_id";
0054         }
0055     }
0056 
0057     if (!filter.isEmpty()) {
0058         mFilters << filter;
0059         mFilterName = filter.section(QChar::fromLatin1('='), 1);
0060     }
0061 }
0062 
0063 JournaldLocalAnalyzer::~JournaldLocalAnalyzer()
0064 {
0065     watchLogFiles(false);
0066     sd_journal_close(mJournal);
0067     delete mJournalNotifier;
0068 }
0069 
0070 void JournaldLocalAnalyzer::watchLogFiles(bool enabled)
0071 {
0072     if (!mJournalNotifier) {
0073         return;
0074     }
0075     mJournalNotifier->setEnabled(enabled);
0076 
0077     mWorkerMutex.lock();
0078     mForgetWatchers = enabled;
0079     mWorkerMutex.unlock();
0080 
0081     if (enabled) {
0082         auto watcher = new JournalWatcher();
0083         mWorkerMutex.lock();
0084         mJournalWatchers.append(watcher);
0085         mWorkerMutex.unlock();
0086         connect(watcher, &JournalWatcher::finished, this, &JournaldLocalAnalyzer::readJournalInitialFinished);
0087         watcher->setFuture(QtConcurrent::run(&JournaldLocalAnalyzer::readJournal, this, mFilters));
0088     } else {
0089         for (JournalWatcher *watcher : mJournalWatchers) {
0090             watcher->waitForFinished();
0091         }
0092         qDeleteAll(mJournalWatchers);
0093         mJournalWatchers.clear();
0094 
0095         if (mCursor) {
0096             free(mCursor);
0097             mCursor = nullptr;
0098         }
0099     }
0100 }
0101 
0102 QStringList JournaldLocalAnalyzer::units() const
0103 {
0104     return JournaldLocalAnalyzer::unitsStatic();
0105 }
0106 
0107 QStringList JournaldLocalAnalyzer::unitsStatic()
0108 {
0109     return getUniqueFieldValues(QStringLiteral("_SYSTEMD_UNIT"));
0110 }
0111 
0112 QStringList JournaldLocalAnalyzer::syslogIdentifiers() const
0113 {
0114     return JournaldLocalAnalyzer::syslogIdentifiersStatic();
0115 }
0116 
0117 QStringList JournaldLocalAnalyzer::syslogIdentifiersStatic()
0118 {
0119     return getUniqueFieldValues(QStringLiteral("SYSLOG_IDENTIFIER"));
0120 }
0121 
0122 void JournaldLocalAnalyzer::readJournalInitialFinished()
0123 {
0124     readJournalFinished(FullRead);
0125 }
0126 
0127 void JournaldLocalAnalyzer::readJournalUpdateFinished()
0128 {
0129     readJournalFinished(UpdatingRead);
0130 }
0131 
0132 void JournaldLocalAnalyzer::readJournalFinished(ReadingMode readingMode)
0133 {
0134     auto watcher = static_cast<JournalWatcher *>(sender());
0135     if (!watcher) {
0136         return;
0137     }
0138 
0139     QList<JournalEntry> entries = watcher->result();
0140 
0141     if (mParsingPaused) {
0142         qCDebug(KSYSTEMLOG) << "Parsing is paused, discarding journald entries.";
0143     } else if (entries.empty()) {
0144         qCDebug(KSYSTEMLOG) << "Received no entries.";
0145     } else {
0146         mInsertionLocking.lock();
0147         mLogViewModel->startingMultipleInsertions();
0148 
0149         if (FullRead == readingMode) {
0150             Q_EMIT statusBarChanged(i18n("Reading journald entries..."));
0151             // Start displaying the loading bar.
0152             Q_EMIT readFileStarted(*mLogMode, LogFile(), 0, 1);
0153         }
0154 
0155         // Add journald entries to the model.
0156         int const entriesInserted = updateModel(entries, readingMode);
0157 
0158         mLogViewModel->endingMultipleInsertions(readingMode, entriesInserted);
0159 
0160         if (FullRead == readingMode) {
0161             Q_EMIT statusBarChanged(i18n("Journald entries loaded successfully."));
0162 
0163             // Stop displaying the loading bar.
0164             Q_EMIT readEnded();
0165         }
0166 
0167         // Inform LogManager that new lines have been added.
0168         Q_EMIT logUpdated(entriesInserted);
0169 
0170         mInsertionLocking.unlock();
0171     }
0172 
0173     mWorkerMutex.lock();
0174     if (mForgetWatchers) {
0175         mJournalWatchers.removeAll(watcher);
0176         watcher->deleteLater();
0177     }
0178     mWorkerMutex.unlock();
0179 }
0180 
0181 void JournaldLocalAnalyzer::journalDescriptorUpdated(int fd)
0182 {
0183     qCDebug(KSYSTEMLOG) << "Journal was updated.";
0184     QFile file;
0185     file.open(fd, QIODevice::ReadOnly);
0186     file.readAll();
0187     file.close();
0188 
0189     if (mParsingPaused) {
0190         qCDebug(KSYSTEMLOG) << "Parsing is paused, will not fetch new journald entries.";
0191         return;
0192     }
0193 
0194     auto watcher = new JournalWatcher();
0195     mWorkerMutex.lock();
0196     mJournalWatchers.append(watcher);
0197     mWorkerMutex.unlock();
0198     connect(watcher, &JournalWatcher::finished, this, &JournaldLocalAnalyzer::readJournalUpdateFinished);
0199     watcher->setFuture(QtConcurrent::run(&JournaldLocalAnalyzer::readJournal, this, mFilters));
0200 }
0201 
0202 QList<JournaldLocalAnalyzer::JournalEntry> JournaldLocalAnalyzer::readJournal(const QStringList &filters)
0203 {
0204     QMutexLocker const mutexLocker(&mWorkerMutex);
0205     QList<JournalEntry> entryList;
0206     sd_journal *journal;
0207 
0208     if (!mFilterName.isEmpty()) {
0209         Q_EMIT statusChanged(mFilterName);
0210     }
0211 
0212     int res = sd_journal_open(&journal, mJournalFlags);
0213     if (res < 0) {
0214         qCWarning(KSYSTEMLOG) << "Failed to access the journal.";
0215         return QList<JournalEntry>();
0216     }
0217 
0218     if (prepareJournalReading(journal, filters)) {
0219         // Iterate over filtered entries.
0220         while (1) {
0221             JournalEntry entry;
0222             res = sd_journal_next(journal);
0223             if (res < 0) {
0224                 qCWarning(KSYSTEMLOG) << "Failed to access next journal entry.";
0225                 break;
0226             }
0227             if (res == 0) {
0228                 // Reached last journal entry.
0229                 break;
0230             }
0231             entry = readJournalEntry(journal);
0232             entryList.append(entry);
0233         }
0234 
0235         free(mCursor);
0236         sd_journal_get_cursor(journal, &mCursor);
0237     }
0238 
0239     sd_journal_close(journal);
0240     if (!entryList.empty()) {
0241         qCDebug(KSYSTEMLOG) << "Read" << entryList.size() << "journal entries.";
0242     }
0243     return entryList;
0244 }
0245 
0246 bool JournaldLocalAnalyzer::prepareJournalReading(sd_journal *journal, const QStringList &filters)
0247 {
0248     int res;
0249 
0250     // Set entries filter.
0251     for (const QString &filter : filters) {
0252         res = sd_journal_add_match(journal, filter.toUtf8().constData(), 0);
0253         if (res < 0) {
0254             qCWarning(KSYSTEMLOG) << "Failed to set journal filter.";
0255             return false;
0256         }
0257     }
0258 
0259     // Go to the latest journal entry.
0260     res = sd_journal_seek_tail(journal);
0261     if (res < 0) {
0262         qCWarning(KSYSTEMLOG) << "Failed to seek journal tail.";
0263         return false;
0264     }
0265 
0266     // Read number of entries allowed by KSystemLog configuration.
0267     int const maxEntriesNum = KSystemLogConfig::maxLines();
0268 
0269     // Seek to cursor.
0270     if (mCursor) {
0271         int entriesNum = 0;
0272         // Continue searching for the oldest entry until
0273         // either cursor is found or maximum number of entries is traversed.
0274         while (entriesNum < maxEntriesNum) {
0275             entriesNum++;
0276 
0277             res = sd_journal_previous(journal);
0278             if (res < 0) {
0279                 qCWarning(KSYSTEMLOG) << "Failed to seek previous journal entry.";
0280                 return false;
0281             }
0282 
0283             res = sd_journal_test_cursor(journal, mCursor);
0284             if (res > 0) {
0285                 if (entriesNum == 1) {
0286                     // No new entries are found.
0287                     return false;
0288                 }
0289                 // Latest journal entry before journal update is found.
0290                 break;
0291             }
0292         }
0293     } else {
0294         // Jump over maxEntriesNum entries backwards from the end of the journal.
0295         res = sd_journal_previous_skip(journal, maxEntriesNum + 1);
0296         if (res < 0) {
0297             // Seek failed. Read entries from the beginning.
0298             res = sd_journal_seek_head(journal);
0299             if (res < 0) {
0300                 qCWarning(KSYSTEMLOG) << "Failed to seek journal head.";
0301                 return false;
0302             }
0303         }
0304     }
0305     return true;
0306 }
0307 
0308 JournaldLocalAnalyzer::JournalEntry JournaldLocalAnalyzer::readJournalEntry(sd_journal *journal) const
0309 {
0310     // Reads a single journal entry into JournalEntry structure.
0311     JournalEntry entry;
0312     const void *data;
0313     size_t length;
0314     uint64_t time;
0315     int res;
0316 
0317     res = sd_journal_get_realtime_usec(journal, &time);
0318     if (res == 0) {
0319         entry.date.setMSecsSinceEpoch(time / 1000);
0320     }
0321 
0322     res = sd_journal_get_data(journal, "SYSLOG_IDENTIFIER", &data, &length);
0323     if (res == 0) {
0324         entry.unit = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1);
0325     } else {
0326         res = sd_journal_get_data(journal, "_SYSTEMD_UNIT", &data, &length);
0327         if (res == 0) {
0328             entry.unit = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1);
0329         }
0330     }
0331 
0332     res = sd_journal_get_data(journal, "MESSAGE", &data, &length);
0333     if (res == 0) {
0334         entry.message = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1);
0335         entry.message.remove(QRegularExpression(QLatin1String(ConsoleColorEscapeSequence)));
0336     }
0337 
0338     res = sd_journal_get_data(journal, "PRIORITY", &data, &length);
0339     if (res == 0) {
0340         entry.priority = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1).toInt();
0341     }
0342 
0343     res = sd_journal_get_data(journal, "_BOOT_ID", &data, &length);
0344     if (res == 0) {
0345         entry.bootID = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1);
0346     }
0347 
0348     return entry;
0349 }
0350 
0351 QStringList JournaldLocalAnalyzer::getUniqueFieldValues(const QString &id, int flags)
0352 {
0353     QStringList units;
0354     sd_journal *journal;
0355     int res = sd_journal_open(&journal, flags);
0356     if (res == 0) {
0357         const void *data;
0358         size_t length;
0359 
0360         // Get all unique field values. The order is not defined.
0361         res = sd_journal_query_unique(journal, id.toUtf8().constData());
0362         if (res == 0) {
0363             SD_JOURNAL_FOREACH_UNIQUE(journal, data, length)
0364             {
0365                 const QString unit = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1);
0366                 if (unit.startsWith(QLatin1String("systemd-coredump@"))) {
0367                     continue; // these never contain any log information, and can easily fill up menu
0368                 }
0369                 units.append(unit);
0370             }
0371         }
0372 
0373         units.removeDuplicates();
0374         units.sort();
0375         sd_journal_close(journal);
0376     } else {
0377         qCWarning(KSYSTEMLOG) << "Failed to open the journal and extract unique values for field" << id;
0378     }
0379     return units;
0380 }
0381 
0382 #include "moc_journaldLocalAnalyzer.cpp"