File indexing completed on 2024-05-05 17:55:36

0001 /*******************************************************************************
0002  * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>                     *
0003  *                                                                             *
0004  * This program is free software: you can redistribute it and/or modify it     *
0005  * under the terms of the GNU General Public License as published by the Free  *
0006  * Software Foundation, either version 2 of the License, or (at your option)   *
0007  * any later version.                                                          *
0008  *                                                                             *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT *
0010  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
0011  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    *
0012  * more details.                                                               *
0013  *                                                                             *
0014  * You should have received a copy of the GNU General Public License along     *
0015  * with this program. If not, see <http://www.gnu.org/licenses/>.              *
0016  *******************************************************************************/
0017 
0018 #include "unitmodel.h"
0019 
0020 #include <KLocalizedString>
0021 #include <KColorScheme>
0022 
0023 #include <QColor>
0024 #include <QIcon>
0025 #include <QtDBus/QtDBus>
0026 
0027 #include <systemd/sd-journal.h>
0028 
0029 UnitModel::UnitModel(QObject *parent)
0030     : QAbstractTableModel(parent)
0031 {
0032 }
0033 
0034 UnitModel::UnitModel(QObject *parent, const QVector<SystemdUnit> *list, QString userBusPath)
0035     : QAbstractTableModel(parent)
0036 {
0037     m_unitList = list;
0038     m_userBus = userBusPath;
0039 }
0040 
0041 int UnitModel::rowCount(const QModelIndex &) const
0042 {
0043     return m_unitList->size();
0044 }
0045 
0046 int UnitModel::columnCount(const QModelIndex &) const
0047 {
0048     return 4;
0049 }
0050 
0051 QVariant UnitModel::headerData(int section, Qt::Orientation orientation, int role) const
0052 {
0053     if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
0054         return i18n("Unit");
0055     } else if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 1) {
0056         return i18n("Load State");
0057     } else if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 2) {
0058         return i18n("Active State");
0059     } else if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 3) {
0060         return i18n("Unit State");
0061     }
0062     return QVariant();
0063 }
0064 
0065 QVariant UnitModel::data(const QModelIndex & index, int role) const
0066 {
0067 
0068     if (!index.isValid()) {
0069         return QVariant();
0070     }
0071 
0072     if (role == Qt::DisplayRole)
0073     {
0074         if (index.column() == 0)
0075             return m_unitList->at(index.row()).id;
0076         else if (index.column() == 1)
0077             return m_unitList->at(index.row()).load_state;
0078         else if (index.column() == 2)
0079             return m_unitList->at(index.row()).active_state;
0080         else if (index.column() == 3)
0081             return m_unitList->at(index.row()).sub_state;
0082     }
0083 
0084     else if (role == Qt::ForegroundRole)
0085     {
0086         const KColorScheme scheme(QPalette::Normal);
0087         if (m_unitList->at(index.row()).active_state == QLatin1String("active"))
0088             return scheme.foreground(KColorScheme::PositiveText);
0089         else if (m_unitList->at(index.row()).active_state == QLatin1String("failed"))
0090             return scheme.foreground(KColorScheme::NegativeText);
0091         else if (m_unitList->at(index.row()).active_state == QLatin1String("-"))
0092             return scheme.foreground(KColorScheme::InactiveText);
0093         else
0094             return QVariant();
0095     }
0096 
0097     else if (role == Qt::DecorationRole)
0098     {
0099         if (index.column() == 0) {
0100             if (m_unitList->at(index.row()).active_state == QLatin1String("active")) {
0101                 return QIcon::fromTheme(QStringLiteral("emblem-success"));
0102             } else if (m_unitList->at(index.row()).active_state == QLatin1String("inactive")) {
0103                 return QIcon::fromTheme(QStringLiteral("emblem-pause"));
0104             } else if (m_unitList->at(index.row()).active_state == QLatin1String("failed")) {
0105                 return QIcon::fromTheme(QStringLiteral("emblem-error"));
0106             } else if (m_unitList->at(index.row()).active_state == QLatin1String("-")) {
0107                 return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
0108             } else {
0109                 return QVariant();
0110             }
0111         }
0112     }
0113 
0114     else if (role == Qt::ToolTipRole)
0115     {
0116         const QString selUnit = m_unitList->at(index.row()).id;
0117         const QString selUnitPath = m_unitList->at(index.row()).unit_path.path();
0118         const QString selUnitFile = m_unitList->at(index.row()).unit_file;
0119 
0120         QString toolTipText;
0121         toolTipText.append(QStringLiteral("<b>%1</b><hr>").arg(selUnit));
0122 
0123         // Create a DBus interface
0124         QDBusConnection bus(QDBusConnection::systemBus());
0125         if (!m_userBus.isEmpty()) {
0126             bus = QDBusConnection::connectToBus(m_userBus, QStringLiteral("org.freedesktop.systemd1"));
0127         }
0128         QDBusInterface *iface;
0129 
0130         // Use the DBus interface to get unit properties
0131         if (!selUnitPath.isEmpty())
0132         {
0133             // Unit has a valid path
0134 
0135             iface = new QDBusInterface (QStringLiteral("org.freedesktop.systemd1"),
0136                                         selUnitPath,
0137                                         QStringLiteral("org.freedesktop.systemd1.Unit"),
0138                                         bus);
0139             if (iface->isValid())
0140             {
0141                 // Unit has a valid unit DBus object
0142                 toolTipText.append(i18n("<b>Description: </b>"));
0143                 toolTipText.append(iface->property("Description").toString());
0144 
0145                 const QString unitFilePath = iface->property("FragmentPath").toString();
0146                 if (!unitFilePath.isEmpty()) {
0147                     toolTipText.append(i18n("<br><b>Unit file: </b>%1", unitFilePath));
0148                     toolTipText.append(i18n("<br><b>Unit file state: </b>"));
0149                     toolTipText.append(iface->property("UnitFileState").toString());
0150                 }
0151 
0152                 const QString sourcePath = iface->property("SourcePath").toString();
0153                 if (!sourcePath.isEmpty()) {
0154                     toolTipText.append(i18n("<br><b>Source path: </b>%1", sourcePath));
0155                 }
0156 
0157                 qulonglong ActiveEnterTimestamp = iface->property("ActiveEnterTimestamp").toULongLong();
0158                 toolTipText.append(i18n("<br><b>Activated: </b>"));
0159                 if (ActiveEnterTimestamp == 0)
0160                     toolTipText.append(QStringLiteral("n/a"));
0161                 else
0162                 {
0163                     QDateTime timeActivated;
0164                     timeActivated.setMSecsSinceEpoch(ActiveEnterTimestamp/1000);
0165                     toolTipText.append(timeActivated.toString());
0166                 }
0167 
0168                 qulonglong InactiveEnterTimestamp = iface->property("InactiveEnterTimestamp").toULongLong();
0169                 toolTipText.append(i18n("<br><b>Deactivated: </b>"));
0170                 if (InactiveEnterTimestamp == 0)
0171                     toolTipText.append(QStringLiteral("n/a"));
0172                 else
0173                 {
0174                     QDateTime timeDeactivated;
0175                     timeDeactivated.setMSecsSinceEpoch(InactiveEnterTimestamp/1000);
0176                     toolTipText.append(timeDeactivated.toString());
0177                 }
0178             }
0179             delete iface;
0180 
0181         } else {
0182 
0183             // Unit does not have a valid unit DBus object (typically unloaded units).
0184             // Retrieve UnitFileState from Manager object.
0185 
0186             iface = new QDBusInterface (QStringLiteral("org.freedesktop.systemd1"),
0187                                         QStringLiteral("/org/freedesktop/systemd1"),
0188                                         QStringLiteral("org.freedesktop.systemd1.Manager"),
0189                                         bus);
0190             QList<QVariant> args;
0191             args << selUnit;
0192 
0193             toolTipText.append(i18n("<b>Unit file: </b>"));
0194             if (!selUnitFile.isEmpty()) {
0195                 toolTipText.append(selUnitFile);
0196             }
0197 
0198             toolTipText.append(i18n("<br><b>Unit file state: </b>"));
0199             if (!selUnitFile.isEmpty()) {
0200                 toolTipText.append(iface->callWithArgumentList(QDBus::AutoDetect, QStringLiteral("GetUnitFileState"), args).arguments().at(0).toString());
0201             }
0202 
0203             delete iface;
0204         }
0205 
0206         // Journal entries for units
0207         toolTipText.append(i18n("<hr><b>Last log entries:</b>"));
0208         QStringList log = getLastJrnlEntries(selUnit);
0209         if (log.isEmpty()) {
0210             toolTipText.append(i18n("<br><i>No log entries found for this unit.</i>"));
0211         } else {
0212             for(int i = log.count()-1; i >= 0; --i)
0213             {
0214                 if (!log.at(i).isEmpty())
0215                     toolTipText.append(QStringLiteral("<br>%1").arg(log.at(i)));
0216             }
0217         }
0218 
0219         toolTipText.append(QStringLiteral("</FONT"));
0220 
0221         return toolTipText;
0222     }
0223 
0224     return QVariant();
0225 }
0226 
0227 QStringList UnitModel::getLastJrnlEntries(QString unit) const
0228 {
0229     QString match1, match2;
0230     int r, jflags;
0231     QStringList reply;
0232     const void *data;
0233     size_t length;
0234     uint64_t time;
0235     sd_journal *journal;
0236 
0237     if (!m_userBus.isEmpty())
0238     {
0239         match1 = QStringLiteral("USER_UNIT=%1").arg(unit);
0240         jflags = (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_CURRENT_USER);
0241     }
0242     else
0243     {
0244         match1 = QStringLiteral("_SYSTEMD_UNIT=%1").arg(unit);
0245         match2 = QStringLiteral("UNIT=%1").arg(unit);
0246         jflags = (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM);
0247     }
0248 
0249     r = sd_journal_open(&journal, jflags);
0250     if (r != 0)
0251     {
0252         qDebug() << "Failed to open journal";
0253         return reply;
0254     }
0255 
0256     sd_journal_flush_matches(journal);
0257 
0258     r = sd_journal_add_match(journal, match1.toUtf8(), 0);
0259     if (r != 0)
0260         return reply;
0261 
0262     if (!match2.isEmpty())
0263     {
0264         sd_journal_add_disjunction(journal);
0265         r = sd_journal_add_match(journal, match2.toUtf8(), 0);
0266         if (r != 0)
0267             return reply;
0268     }
0269 
0270 
0271     r = sd_journal_seek_tail(journal);
0272     if (r != 0)
0273         return reply;
0274 
0275     // Fetch the last 5 entries
0276     for (int i = 0; i < 5; ++i)
0277     {
0278         r = sd_journal_previous(journal);
0279         if (r == 1)
0280         {
0281             QString line;
0282 
0283             // Get the date and time
0284             r = sd_journal_get_realtime_usec(journal, &time);
0285             if (r == 0)
0286             {
0287                 QDateTime date;
0288                 date.setMSecsSinceEpoch(time/1000);
0289                 line.append(date.toString(QStringLiteral("yyyy.MM.dd hh:mm")));
0290             }
0291 
0292             // Color messages according to priority
0293             r = sd_journal_get_data(journal, "PRIORITY", &data, &length);
0294             if (r == 0)
0295             {
0296                 int prio = QString::fromUtf8((const char *)data, length).section(QLatin1Char('='),1).toInt();
0297                 if (prio <= 3)
0298                     line.append(QStringLiteral("<span style='color:tomato;'>"));
0299                 else if (prio == 4)
0300                     line.append(QStringLiteral("<span style='color:khaki;'>"));
0301                 else
0302                     line.append(QStringLiteral("<span style='color:palegreen;'>"));
0303             }
0304 
0305             // Get the message itself
0306             r = sd_journal_get_data(journal, "MESSAGE", &data, &length);
0307             if (r == 0)
0308             {
0309                 line.append(QStringLiteral(": %1</span>").arg(QString::fromUtf8((const char *)data, length).section(QLatin1Char('='),1)));
0310                 if (line.length() > 195)
0311                     line = QStringLiteral("%1...</span>").arg(line.left(195));
0312                 reply << line;
0313             }
0314         }
0315         else // previous failed, no more entries
0316             return reply;
0317     }
0318 
0319     sd_journal_close(journal);
0320 
0321     return reply;
0322 }