/* * Copyright 2016 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "consolelog.h" #include #include #include #include #include #include namespace { int wait_fd(std::function fn) { int ret = -1; bool fd_blocked = false; do { ret = fn(); fd_blocked = (errno == EINTR || errno == EBUSY); if (fd_blocked) QThread::msleep(10); } while (ret < 0); return ret; } #define PIPE_WAIT(val) wait_fd(std::bind(pipe, val)) #define DUP_WAIT(fd) wait_fd(std::bind(dup, fd)) #define DUP2_WAIT(fd1, fd2) wait_fd(std::bind(dup2, fd1, fd2)) #define CLOSE_WAIT(fd) wait_fd(std::bind(close, fd)) } LogRedirector::LogRedirector() : m_ref(0) , m_stop(false) { setObjectName("ConsoleLog"); // make stdout & stderr streams unbuffered // so that we don't need to flush the streams // before capture and after capture // (fflush can cause a deadlock if the stream is currently being used) setvbuf(stdout,NULL,_IONBF,0); setvbuf(stderr,NULL,_IONBF,0); } void LogRedirector::run() { PIPE_WAIT(m_pipe); int oldStdOut = DUP_WAIT(fileno(stdout)); int oldStdErr = DUP_WAIT(fileno(stderr)); DUP2_WAIT(m_pipe[WRITE], fileno(stdout)); DUP2_WAIT(m_pipe[WRITE], fileno(stderr)); CLOSE_WAIT(m_pipe[WRITE]); while(true) { { QMutexLocker lock(&m_mutex); if (m_stop) break; } checkLog(); QThread::msleep(50); } DUP2_WAIT(oldStdOut, fileno(stdout)); DUP2_WAIT(oldStdErr, fileno(stderr)); CLOSE_WAIT(oldStdOut); CLOSE_WAIT(oldStdErr); CLOSE_WAIT(m_pipe[READ]); } void LogRedirector::checkLog() { // Do not allow read to block with no data. // If we stop the thread while waiting a read, // it will block the main thread waiting for this thread to stop. int count = 0; ioctl(m_pipe[READ], FIONREAD, &count); if (count <= 0) return; std::string captured; std::string buf; const int bufSize = 1024; buf.resize(bufSize); int bytesRead = 0; bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize); while(bytesRead == bufSize) { captured += buf; bytesRead = 0; bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize); } if (bytesRead > 0) { buf.resize(bytesRead); captured += buf; } if (!captured.empty()) { Q_EMIT log(QString::fromStdString(captured)); } } LogRedirector *LogRedirector::instance() { static LogRedirector* log = nullptr; if (!log) { log = new LogRedirector(); } return log; } void LogRedirector::add(ConsoleLog* logger) { QMutexLocker lock(&m_mutex); connect(this, &LogRedirector::log, logger, &ConsoleLog::logged, Qt::UniqueConnection); m_ref++; if (!LogRedirector::instance()->isRunning()) { m_stop = false; LogRedirector::instance()->start(); } } void LogRedirector::remove(ConsoleLog* logger) { QMutexLocker lock(&m_mutex); disconnect(this, &LogRedirector::log, logger, &ConsoleLog::logged); m_ref = qMax(m_ref-1, 0); if (m_ref == 0 && LogRedirector::instance()->isRunning()) { m_stop = true; lock.unlock(); LogRedirector::instance()->wait(); } } ConsoleLog::ConsoleLog(QObject *parent) : QObject(parent) , m_enabled(false) , m_maxLines(60) { auto updateEnabled = [this]() { if (m_enabled) { LogRedirector::instance()->add(this); } else { LogRedirector::instance()->remove(this); } }; connect(this, &ConsoleLog::enabledChanged, this, updateEnabled); } ConsoleLog::~ConsoleLog() { if (m_enabled) { LogRedirector::instance()->remove(this); } } void ConsoleLog::setEnabled(bool enabled) { if (m_enabled == enabled) { return; } m_enabled = enabled; Q_EMIT enabledChanged(); } QString ConsoleLog::out() const { return m_out.join("\n"); } void ConsoleLog::setMaxLines(int maxLines) { if (m_maxLines == maxLines) { return; } m_maxLines = maxLines; while (m_out.count() > m_maxLines) { m_out.removeLast(); } Q_EMIT outChanged(); Q_EMIT maxLinesChanged(); } void ConsoleLog::logged(QString captured) { QStringList li = captured.split("\n", QString::SkipEmptyParts); li << m_out; m_out = li; while (m_out.count() > m_maxLines) { m_out.removeLast(); } Q_EMIT outChanged(); }