/* Copyright 2006-2026 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #include "logring.h" #include #ifdef Q_OS_WIN #include #else #include #include #endif static_assert(std::atomic::is_always_lock_free, "LogRing::Entry::length must be a lock-free atomic -- " "dumpToFd() reads it from a signal handler and must never block."); namespace { /** @brief writeAllSignalSafe Loops until length bytes have been written to fd or an unrecoverable error occurs. write(2) may write fewer bytes than requested and may return EINTR -- both are *more* likely from inside a signal handler than in normal code, so a single write() call is not enough here. Async-signal-safe: only calls write(2)/errno, nothing else. */ void writeAllSignalSafe(int fd, const char *data, int length) noexcept { int remaining = length; const char *p = data; while (remaining > 0) { #ifdef Q_OS_WIN const int n = _write(fd, p, static_cast(remaining)); if (n <= 0) { return; } #else const ssize_t n = ::write(fd, p, static_cast(remaining)); if (n < 0) { if (errno == EINTR) { continue; } return; // unrecoverable -- give up silently, never block/throw } if (n == 0) { return; } #endif p += n; remaining -= static_cast(n); } } } // namespace LogRing::LogRing() : // The sized constructor value-initialises each Entry in place; unlike // resize(), it doesn't require Entry to be move/copy-constructible, // which std::atomic deliberately never is. The only allocation // this class ever does. m_entries(kCapacityEntries) { } void LogRing::append(const QByteArray &line) noexcept { static const char kMarker[] = "...[ring-truncated]\n"; const int marker_len = static_cast(sizeof(kMarker)) - 1; const quint64 idx = m_write_cursor.fetch_add(1, std::memory_order_relaxed); Entry &slot = m_entries[static_cast(idx % static_cast(kCapacityEntries))]; // Zero the length first so a concurrent reader landing on this exact // slot mid-copy sees "not ready" rather than the previous lap's // (now-being-overwritten) content at a stale length. slot.length.store(0, std::memory_order_relaxed); int len; if (line.size() < kEntryBytes) { std::memcpy(slot.data, line.constData(), static_cast(line.size())); len = line.size(); } else { const int keep = kEntryBytes - marker_len; std::memcpy(slot.data, line.constData(), static_cast(keep)); std::memcpy(slot.data + keep, kMarker, static_cast(marker_len)); len = kEntryBytes; } slot.length.store(len, std::memory_order_release); } QVector LogRing::snapshot() const { const quint64 cursor = m_write_cursor.load(std::memory_order_acquire); const quint64 cap = static_cast(kCapacityEntries); const quint64 count = (cursor < cap) ? cursor : cap; const quint64 start = (cursor < cap) ? 0 : (cursor - cap); QVector result; result.reserve(static_cast(count)); for (quint64 i = 0; i < count; ++i) { const Entry &slot = m_entries[static_cast((start + i) % cap)]; const int len = slot.length.load(std::memory_order_acquire); if (len > 0) { result.append(QByteArray(slot.data, len)); } } return result; } void LogRing::dumpToFd(int fd) const noexcept { const quint64 cursor = m_write_cursor.load(std::memory_order_acquire); const quint64 cap = static_cast(kCapacityEntries); const quint64 count = (cursor < cap) ? cursor : cap; const quint64 start = (cursor < cap) ? 0 : (cursor - cap); for (quint64 i = 0; i < count; ++i) { const Entry &slot = m_entries[static_cast((start + i) % cap)]; const int len = slot.length.load(std::memory_order_acquire); if (len > 0) { writeAllSignalSafe(fd, slot.data, len); } } } void LogRing::clear() { m_write_cursor.store(0, std::memory_order_relaxed); for (auto &entry : m_entries) { entry.length.store(0, std::memory_order_relaxed); } }