/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "journaldwatcher.h" #include #include #include #include namespace ProjectExplorer { JournaldWatcher *JournaldWatcher::m_instance = 0; namespace Internal { class JournaldWatcherPrivate { public: JournaldWatcherPrivate() : m_journalContext(0), m_notifier(0) { } ~JournaldWatcherPrivate() { teardown(); } bool setup(); void teardown(); JournaldWatcher::LogEntry retrieveEntry(); class SubscriberInformation { public: SubscriberInformation(QObject *sr, const JournaldWatcher::Subscription &sn) : subscriber(sr), subscription(sn) { } QObject *subscriber; JournaldWatcher::Subscription subscription; }; QList m_subscriptions; sd_journal *m_journalContext; QSocketNotifier *m_notifier; }; bool JournaldWatcherPrivate::setup() { QTC_ASSERT(!m_journalContext, return false); int r = sd_journal_open(&m_journalContext, 0); if (r != 0) return false; r = sd_journal_seek_tail(m_journalContext); if (r != 0) return false; // Work around https://bugs.freedesktop.org/show_bug.cgi?id=64614 sd_journal_previous(m_journalContext); int fd = sd_journal_get_fd(m_journalContext); if (fd < 0) return false; m_notifier = new QSocketNotifier(fd, QSocketNotifier::Read); return true; } void JournaldWatcherPrivate::teardown() { delete m_notifier; m_notifier = 0; if (m_journalContext) { sd_journal_close(m_journalContext); m_journalContext = 0; } } JournaldWatcher::LogEntry JournaldWatcherPrivate::retrieveEntry() { JournaldWatcher::LogEntry result; // Advance: int r = sd_journal_next(m_journalContext); if (r == 0) return result; const void *rawData; size_t length; SD_JOURNAL_FOREACH_DATA(m_journalContext, rawData, length) { QByteArray tmp = QByteArray::fromRawData(static_cast(rawData), length); int offset = tmp.indexOf('='); if (offset < 0) continue; result.insert(tmp.left(offset), tmp.mid(offset + 1)); } return result; } } // namespace Internal using namespace Internal; static JournaldWatcherPrivate *d = 0; JournaldWatcher::~JournaldWatcher() { d->teardown(); m_instance = 0; delete d; d = 0; } JournaldWatcher *JournaldWatcher::instance() { return m_instance; } const QByteArray &JournaldWatcher::machineId() { static QByteArray id; if (id.isEmpty()) { sd_id128 sdId; if (sd_id128_get_machine(&sdId) == 0) { id.resize(32); sd_id128_to_string(sdId, id.data()); } } return id; } bool JournaldWatcher::subscribe(QObject *subscriber, const Subscription &subscription) { // Check that we do not have that subscriber yet: int pos = Utils::indexOf(d->m_subscriptions, [subscriber](const JournaldWatcherPrivate::SubscriberInformation &info) { return info.subscriber == subscriber; }); QTC_ASSERT(pos >= 0, return false); d->m_subscriptions.append(JournaldWatcherPrivate::SubscriberInformation(subscriber, subscription)); connect(subscriber, &QObject::destroyed, m_instance, &JournaldWatcher::unsubscribe); return true; } void JournaldWatcher::unsubscribe(QObject *subscriber) { int pos = Utils::indexOf(d->m_subscriptions, [subscriber](const JournaldWatcherPrivate::SubscriberInformation &info) { return info.subscriber == subscriber; }); if (pos < 0) return; d->m_subscriptions.removeAt(pos); } JournaldWatcher::JournaldWatcher() { QTC_ASSERT(!m_instance, return); d = new JournaldWatcherPrivate; m_instance = this; if (!d->setup()) d->teardown(); else connect(d->m_notifier, &QSocketNotifier::activated, m_instance, &JournaldWatcher::handleEntry); m_instance->handleEntry(); // advance to the end of file... } void JournaldWatcher::handleEntry() { if (!d->m_notifier) return; if (sd_journal_process(d->m_journalContext) != SD_JOURNAL_APPEND) return; LogEntry logEntry; forever { logEntry = d->retrieveEntry(); if (logEntry.isEmpty()) break; foreach (const JournaldWatcherPrivate::SubscriberInformation &info, d->m_subscriptions) info.subscription(logEntry); if (logEntry.value(QByteArrayLiteral("_MACHINE_ID")) != machineId()) continue; const QByteArray pid = logEntry.value(QByteArrayLiteral("_PID")); if (pid.isEmpty()) continue; quint64 pidNum = QString::fromLatin1(pid).toInt(); QString message = QString::fromUtf8(logEntry.value(QByteArrayLiteral("MESSAGE"))); message.append(QLatin1Char('\n')); // Add newline. emit journaldOutput(pidNum, message); } } } // namespace ProjectExplorer