/* * Copyright (C) 2015 Malte Veerman * * 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; either version 2 of the License, or * (at your option) any later version. * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "loader.h" #include "hwmon.h" #include "fancontrolaction.h" #include #include #include #include #include #include #include #include #define HWMON_PATH "/sys/class/hwmon" #ifndef STANDARD_CONFIG_FILE #define STANDARD_CONFIG_FILE "/etc/fancontrol" #endif namespace Fancontrol { Loader::Loader(QObject *parent) : QObject(parent), m_reactivateAfterTesting(true), m_interval(10), m_configUrl(QUrl::fromLocalFile(QStringLiteral(STANDARD_CONFIG_FILE))), m_timer(new QTimer(this)), m_sensorsDetected(false) { parseHwmons(); m_timer->setSingleShot(false); m_timer->start(1000); connect(m_timer, &QTimer::timeout, this, &Loader::updateSensors); } void Loader::parseHwmons() { const auto hwmonDir = QDir(QStringLiteral(HWMON_PATH)); QStringList list; if (hwmonDir.isReadable()) list = hwmonDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot); else if (hwmonDir.exists()) { setError(i18n("%1 is not readable!", QStringLiteral(HWMON_PATH)), true); return; } else { setError(i18n("%1 does not exist!", QStringLiteral(HWMON_PATH)), true); return; } QStringList dereferencedList; while (!list.isEmpty()) dereferencedList << QFile::symLinkTarget(hwmonDir.absoluteFilePath(list.takeFirst())); foreach (const auto &hwmon, m_hwmons) { if (!dereferencedList.contains(hwmon->path())) { hwmon->deleteLater(); m_hwmons.removeOne(hwmon); emit hwmonsChanged(); } else hwmon->initialize(); } foreach (const auto &hwmonPath, dereferencedList) { auto hwmonExists = false; foreach (const auto &hwmon, m_hwmons) { if (hwmon->path() == hwmonPath) { hwmonExists = true; break; } } if (!hwmonExists) { auto newHwmon = new Hwmon(hwmonPath, this); if (newHwmon->isValid()) { connect(this, &Loader::sensorsUpdateNeeded, newHwmon, &Hwmon::updateSensors); m_hwmons << newHwmon; emit hwmonsChanged(); } else delete newHwmon; } } } PwmFan * Loader::getPwmFan(const QPair &indexPair) const { const auto hwmon = m_hwmons.value(indexPair.first, Q_NULLPTR); if (!hwmon) return Q_NULLPTR; return hwmon->pwmFan(indexPair.second); } Temp * Loader::getTemp(const QPair &indexPair) const { const auto hwmon = m_hwmons.value(indexPair.first, Q_NULLPTR); if (!hwmon) return Q_NULLPTR; return hwmon->temp(indexPair.second); } QPair Loader::getEntryNumbers(const QString &entry) { if (entry.isEmpty()) { qWarning() << "Loader::getHwmonNumber(): given empty string."; return QPair(-1, -1); } auto list = entry.split('/', QString::SkipEmptyParts); if (list.size() != 2) { qWarning() << "Invalid entry to parse:" << entry << "Should contain exactly one \'/\'"; return QPair(-1, -1); } auto &hwmon = list[0]; auto &sensor = list[1]; if (!hwmon.startsWith(QStringLiteral("hwmon"))) { qWarning() << "Invalid entry to parse:" << entry << "Should begin with \"hwmon\""; return QPair(-1, -1); } if (!sensor.contains(QRegExp("^(pwm|fan|temp)\\d+"))) { qWarning() << "Invalid entry to parse:" << entry << "\n Sensor should begin with pwm|fan|temp followed by a number"; return QPair(-1, -1); } auto success = false; hwmon.remove(QStringLiteral("hwmon")); sensor.remove(QRegExp("^(pwm|fan|temp)")); sensor.remove(QStringLiteral("_input")); const auto hwmonResult = hwmon.toInt(&success); if (!success) { qWarning() << "Invalid entry to parse:" << entry << "Could not convert" << hwmon << "to int"; return QPair(-1, -1); } const auto sensorResult = sensor.toInt(&success); if (!success) { qWarning() << "Invalid entry to parse:" << entry << "Could not convert" << sensor << "to int"; return QPair(-1, -1); } return QPair(hwmonResult, sensorResult - 1); } void Loader::parseConfigLine(const QString &line, void (PwmFan::*memberSetFunction)(int)) const { if (!memberSetFunction) { qWarning() << "Loader::parseConfigLine(): Null for member function pointer"; return; } const auto entries = line.split(' '); foreach (const auto &entry, entries) { const auto fanValuePair = entry.split('='); if (fanValuePair.size() == 2) { const auto fanString = fanValuePair.at(0); const auto valueString = fanValuePair.at(1); auto success = false; const auto value = valueString.toInt(&success); if (success) { auto fan = getPwmFan(getEntryNumbers(fanString)); if (fan) (fan->*memberSetFunction)(value); } else qWarning() << valueString << "is not an int"; } else qWarning() << "Invalid Entry:" << entry; } } bool Loader::load(const QUrl &url) { QString fileName; if (url.isEmpty()) { qDebug() << "Given empty url. Fallback to" << m_configUrl; fileName = m_configUrl.toLocalFile(); } else if (url.isValid()) { if (url.isLocalFile()) fileName = url.toLocalFile(); else { setError(i18n("%1 is not a local file!", url.toDisplayString())); return false; } } else { setError(i18n("%1 is not a valid url!", url.toDisplayString())); return false; } QTextStream stream; QFile file(fileName); QString fileContent; if (file.open(QFile::ReadOnly | QFile::Text)) { if (!url.isEmpty()) { m_configUrl = url; emit configUrlChanged(); } stream.setDevice(&file); fileContent = stream.readAll(); } else if (file.exists()) { auto action = newFancontrolAction(); if (action.isValid()) { auto map = QVariantMap(); map[QStringLiteral("action")] = QVariant("read"); map[QStringLiteral("filename")] = fileName; action.setArguments(map); auto reply = action.execute(); if (!reply->exec()) { if (reply->error() == 4) { qDebug() << "Aborted by user"; return false; } qDebug() << "Error while loading:" << reply->error(); setError(reply->errorString() + reply->errorText(), true); return false; } else { if (!url.isEmpty()) { m_configUrl = url; emit configUrlChanged(); } fileContent = reply->data().value(QStringLiteral("content")).toString(); } } else setError(i18n("Action not supported! Try running the application as root."), true); } else { if (!url.isEmpty()) { emit invalidConfigUrl(); setError(i18n("%1 does not exist!", file.fileName())); } return false; } //Disconnect hwmons for performance reasons //They get reconnected later foreach (const auto &hwmon, m_hwmons) { disconnect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::createConfigFile); foreach (const auto &pwmFan, hwmon->pwmFans()) { qobject_cast(pwmFan)->reset(); } } stream.setString(&fileContent); auto lines = QStringList(); do { auto line(stream.readLine()); if (line.startsWith('#') || line.trimmed().isEmpty()) continue; const auto offset = line.indexOf('#'); if (offset != -1) line.truncate(offset); line = line.simplified(); lines << line; } while(!stream.atEnd()); foreach (auto line, lines) { if (line.startsWith(QStringLiteral("INTERVAL="))) { line.remove(QStringLiteral("INTERVAL=")); auto success = false; const auto interval = line.toInt(&success); if (success) setInterval(interval, false); else { //Connect hwmons again foreach (const auto &hwmon, m_hwmons) connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::createConfigFile); setError(i18n("Unable to parse interval line: \n %1", line), true); return false; } } else if (line.startsWith(QStringLiteral("FCTEMPS="))) { line.remove(QStringLiteral("FCTEMPS=")); const auto fctemps = line.split(' '); foreach (const auto &fctemp, fctemps) { const auto nameValuePair = fctemp.split('='); if (nameValuePair.size() == 2) { const auto pwm = nameValuePair.at(0); const auto temp = nameValuePair.at(1); const auto pwmPointer = getPwmFan(getEntryNumbers(pwm)); const auto tempPointer = getTemp(getEntryNumbers(temp)); if (pwmPointer && tempPointer) { pwmPointer->setTemp(tempPointer); pwmPointer->setHasTemp(true); pwmPointer->setMinPwm(0); } } else qWarning() << "Invalid entry:" << fctemp; } } else if (line.startsWith(QStringLiteral("DEVNAME="))) { line.remove(QStringLiteral("DEVNAME=")); const auto devnames = line.split(' '); foreach (const auto &devname, devnames) { const auto indexNamePair = devname.split('='); if (indexNamePair.size() == 2) { auto index = indexNamePair.at(0); const auto &name = indexNamePair[1]; auto success = false; index.remove(QStringLiteral("hwmon")); const auto hwmonPointer = m_hwmons.value(index.toInt(&success), Q_NULLPTR); if (!success) { //Connect hwmons again foreach (const auto &hwmon, m_hwmons) connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::createConfigFile); setError(i18n("Can not parse %1", devname), true); return false; } if (!hwmonPointer || hwmonPointer->name().split('.').first() != name) { //Connect hwmons again foreach (const auto &hwmon, m_hwmons) connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::createConfigFile); setError(i18n("Invalid config file!"), true); return false; } } } } else if (line.startsWith(QStringLiteral("MINTEMP="))) { line.remove(QStringLiteral("MINTEMP=")); parseConfigLine(line, &PwmFan::setMinTemp); } else if (line.startsWith(QStringLiteral("MAXTEMP="))) { line.remove(QStringLiteral("MAXTEMP=")); parseConfigLine(line, &PwmFan::setMaxTemp); } else if (line.startsWith(QStringLiteral("MINSTART="))) { line.remove(QStringLiteral("MINSTART=")); parseConfigLine(line, &PwmFan::setMinStart); } else if (line.startsWith(QStringLiteral("MINSTOP="))) { line.remove(QStringLiteral("MINSTOP=")); parseConfigLine(line, &PwmFan::setMinStop); } else if (line.startsWith(QStringLiteral("MINPWM="))) { line.remove(QStringLiteral("MINPWM=")); parseConfigLine(line, &PwmFan::setMinPwm); } else if (line.startsWith(QStringLiteral("MAXPWM="))) { line.remove(QStringLiteral("MAXPWM=")); parseConfigLine(line, &PwmFan::setMaxPwm); } else if (!line.startsWith(QStringLiteral("DEVPATH=")) && !line.startsWith(QStringLiteral("FCFANS="))) { //Connect hwmons again foreach (const auto &hwmon, m_hwmons) connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::createConfigFile); setError(i18n("Unrecognized line in config:\n%1", line), true); return false; } } createConfigFile(); //Connect hwmons again foreach (const auto &hwmon, m_hwmons) connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::createConfigFile); emit configUrlChanged(); return true; } bool Loader::save(const QUrl &url) { QString fileName; if (url.isEmpty()) { qDebug() << "Given empty url. Fallback to " << m_configUrl; fileName = m_configUrl.toLocalFile(); } else if (url.isLocalFile()) fileName = url.toLocalFile(); else { setError(i18n("%1 is not a local file!", url.toDisplayString()), true); return false; } QFile file(fileName); if (file.open(QFile::WriteOnly | QFile::Text)) { QTextStream stream(&file); stream << m_configFile; } else { auto action = newFancontrolAction(); if (action.isValid()) { QVariantMap map; map[QStringLiteral("action")] = QVariant("write"); map[QStringLiteral("filename")] = fileName; map[QStringLiteral("content")] = m_configFile; action.setArguments(map); auto *reply = action.execute(); if (!reply->exec()) { if (reply->error() == 4) { qDebug() << "Aborted by user"; return false; } qDebug() << "Error while saving:" << reply->error(); setError(reply->errorString() + reply->errorText(), true); return false; } } else setError(i18n("Action not supported! Try running the application as root."), true); } return true; } void Loader::createConfigFile() { QList usedHwmons; QList usedFans; foreach (const auto &hwmon, m_hwmons) { if (hwmon->pwmFans().size() > 0 && !usedHwmons.contains(hwmon)) usedHwmons << hwmon; foreach (const auto &fan, hwmon->pwmFans()) { auto pwmFan = qobject_cast(fan); if (pwmFan->hasTemp() && pwmFan->temp() && !pwmFan->testing()) { usedFans << pwmFan; if (!usedHwmons.contains(pwmFan->temp()->parent())) usedHwmons << pwmFan->temp()->parent(); } } } auto configFile = QStringLiteral("# This file was created by Fancontrol-GUI") + QChar(QChar::LineFeed); if (m_interval != 0) configFile += QStringLiteral("INTERVAL=") + QString::number(m_interval) + QChar(QChar::LineFeed); if (!usedHwmons.isEmpty()) { configFile += QStringLiteral("DEVPATH="); foreach (const auto &hwmon, usedHwmons) { auto sanitizedPath = hwmon->path(); sanitizedPath.remove(QRegExp("^/sys/")); sanitizedPath.remove(QRegExp("/hwmon/hwmon\\d\\s*$")); configFile += QStringLiteral("hwmon") + QString::number(hwmon->index()) + "=" + sanitizedPath + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("DEVNAME="); foreach (const auto &hwmon, usedHwmons) configFile += QStringLiteral("hwmon") + QString::number(hwmon->index()) + "=" + hwmon->name().split('.').first() + QChar(QChar::Space); configFile += QChar(QChar::LineFeed); if (!usedFans.isEmpty()) { configFile += QStringLiteral("FCTEMPS="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QStringLiteral("hwmon") + QString::number(pwmFan->temp()->parent()->index()) + "/"; configFile += QStringLiteral("temp") + QString::number(pwmFan->temp()->index()) + QStringLiteral("_input "); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("FCFANS="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("fan") + QString::number(pwmFan->index()) + QStringLiteral("_input "); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("MINTEMP="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QString::number(pwmFan->minTemp()) + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("MAXTEMP="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QString::number(pwmFan->maxTemp()) + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("MINSTART="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QString::number(pwmFan->minStart()) + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("MINSTOP="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QString::number(pwmFan->minStop()) + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("MINPWM="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QString::number(pwmFan->minPwm()) + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); configFile += QStringLiteral("MAXPWM="); foreach (const auto &pwmFan, usedFans) { configFile += QStringLiteral("hwmon") + QString::number(pwmFan->parent()->index()) + "/"; configFile += QStringLiteral("pwm") + QString::number(pwmFan->index()) + "="; configFile += QString::number(pwmFan->maxPwm()) + QChar(QChar::Space); } configFile += QChar(QChar::LineFeed); } } if (configFile != m_configFile) { m_configFile = configFile; emit configFileChanged(); } } void Loader::setInterval(int interval, bool writeNewConfig) { if (interval != m_interval) { m_interval = interval; emit intervalChanged(); if (writeNewConfig) createConfigFile(); } } void Loader::testFans() { foreach (const auto &hwmon, m_hwmons) hwmon->testFans(); } void Loader::abortTestingFans() { foreach (const auto &hwmon, m_hwmons) hwmon->abortTestingFans(); } void Loader::detectSensors() { auto program = QStringLiteral("sensors-detect"); auto arguments = QStringList() << QStringLiteral("--auto"); auto process = new QProcess(this); process->start(program, arguments); connect(process, static_cast(&QProcess::finished), this, static_cast(&Loader::handleDetectSensorsResult)); } void Loader::handleDetectSensorsResult(int exitCode) { auto process = qobject_cast(sender()); if (exitCode) { if (process) setError(process->readAllStandardOutput()); auto action = newFancontrolAction(); if (action.isValid()) { QVariantMap map; map[QStringLiteral("action")] = QVariant("detectSensors"); action.setArguments(map); auto job = action.execute(); connect(job, &KAuth::ExecuteJob::result, this, static_cast(&Loader::handleDetectSensorsResult)); job->start(); } else setError(i18n("Action not supported! Try running the application as root."), true); } else { if (!m_sensorsDetected) { m_sensorsDetected = true; emit sensorsDetectedChanged(); } parseHwmons(); } if (process) process->deleteLater(); } void Loader::handleDetectSensorsResult(KJob *job) { if (job->error()) { if (job->error() == 4) { qDebug() << "Aborted by user"; return; } qDebug() << "Error while detecting sensors:" << job->error(); setError(job->errorString() + job->errorText(), true); } else { if (!m_sensorsDetected) { m_sensorsDetected = true; emit sensorsDetectedChanged(); } parseHwmons(); } } QList Loader::hwmonsAsObjects() const { auto list = QList(); foreach (const auto &hwmon, m_hwmons) list << qobject_cast(hwmon); return list; } void Loader::setError (const QString &error, bool critical) { m_error = error; emit errorChanged(); if (critical) { qCritical() << error; emit criticalError(); } else qWarning() << error; } void Loader::handleTestStatusChanged() { auto testing = false; foreach (const auto &hwmon, m_hwmons) { if (hwmon->testing() == true) { testing = true; break; } } if (!testing && !m_reactivateAfterTesting) return; emit requestSetServiceActive(!testing); } void Loader::setRestartServiceAfterTesting(bool restart) { if (m_reactivateAfterTesting == restart) return; m_reactivateAfterTesting = restart; emit restartServiceAfterTestingChanged(); } }