File indexing completed on 2024-04-28 05:31:35

0001 /*
0002     SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
0003     SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "cgroup.h"
0009 
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QFile>
0013 #include <QPointer>
0014 #include <QRegularExpression>
0015 #include <QRegularExpressionMatch>
0016 #include <QStandardPaths>
0017 #include <QStringView>
0018 #include <QThreadPool>
0019 
0020 #include "process.h"
0021 
0022 using namespace KSysGuard;
0023 
0024 class KSysGuard::CGroupPrivate
0025 {
0026 public:
0027     CGroupPrivate(const QString &_processGroupId)
0028         : processGroupId(_processGroupId)
0029         , service(serviceFromAppId(_processGroupId))
0030     {
0031     }
0032     const QString processGroupId;
0033     const KService::Ptr service;
0034     QList<pid_t> pids;
0035 
0036     static KService::Ptr serviceFromAppId(const QString &appId);
0037 
0038     static QRegularExpression s_appIdFromProcessGroupPattern;
0039     static QString unescapeName(const QString &cgroupId);
0040 
0041     class ReadPidsRunnable;
0042 };
0043 
0044 class CGroupPrivate::ReadPidsRunnable : public QRunnable
0045 {
0046 public:
0047     ReadPidsRunnable(QObject *context, const QString &path, std::function<void(QList<pid_t>)> callback)
0048         : m_context(context)
0049         , m_path(path)
0050         , m_callback(callback)
0051     {
0052     }
0053 
0054     void run() override
0055     {
0056         QFile pidFile(m_path);
0057         pidFile.open(QFile::ReadOnly | QIODevice::Text);
0058         QTextStream stream(&pidFile);
0059 
0060         QList<pid_t> pids;
0061         QString line = stream.readLine();
0062         while (!line.isNull()) {
0063             pids.append(line.toLong());
0064             line = stream.readLine();
0065         }
0066         // Ensure we call the callback on the thread the context object lives on.
0067         if (m_context) {
0068             QMetaObject::invokeMethod(m_context, std::bind(m_callback, pids));
0069         }
0070     }
0071 
0072 private:
0073     QPointer<QObject> m_context;
0074     QString m_path;
0075     std::function<void(QList<pid_t>)> m_callback;
0076 };
0077 
0078 class CGroupSystemInformation
0079 {
0080 public:
0081     CGroupSystemInformation();
0082     QString sysGgroupRoot;
0083 };
0084 
0085 Q_GLOBAL_STATIC(CGroupSystemInformation, s_cGroupSystemInformation)
0086 
0087 // The spec says that the two following schemes are allowed
0088 // - app[-<launcher>]-<ApplicationID>-<RANDOM>.scope
0089 // - app[-<launcher>]-<ApplicationID>[@<RANDOM>].service
0090 // Flatpak's are currently in a cgroup, but they don't follow the specification
0091 // this has been fixed, but this provides some compatibility till that lands
0092 // app vs apps exists because the spec changed.
0093 QRegularExpression
0094     CGroupPrivate::s_appIdFromProcessGroupPattern(QStringLiteral("(apps|app|flatpak)-(?:[^-]*-)?([^-]+(?=-.*\\.scope)|[^@]+(?=(?:@.*)?\\.service))"));
0095 
0096 CGroup::CGroup(const QString &id)
0097     : d(new CGroupPrivate(id))
0098 {
0099 }
0100 
0101 CGroup::~CGroup()
0102 {
0103 }
0104 
0105 QString KSysGuard::CGroup::id() const
0106 {
0107     return d->processGroupId;
0108 }
0109 
0110 KService::Ptr KSysGuard::CGroup::service() const
0111 {
0112     return d->service;
0113 }
0114 
0115 QList<pid_t> CGroup::pids() const
0116 {
0117     return d->pids;
0118 }
0119 
0120 void CGroup::setPids(const QList<pid_t> &pids)
0121 {
0122     d->pids = pids;
0123 }
0124 
0125 void CGroup::requestPids(QObject *context, std::function<void(QList<pid_t>)> callback)
0126 {
0127     QString path = cgroupSysBasePath() + d->processGroupId + QLatin1String("/cgroup.procs");
0128     auto readPidsRunnable = new CGroupPrivate::ReadPidsRunnable(context, path, callback);
0129     QThreadPool::globalInstance()->start(readPidsRunnable);
0130 }
0131 
0132 QString CGroupPrivate::unescapeName(const QString &name)
0133 {
0134     // strings are escaped in the form of \xZZ where ZZ is a two digits in hex representing an ascii code
0135     QString rc = name;
0136     while (true) {
0137         int escapeCharIndex = rc.indexOf(QLatin1Char('\\'));
0138         if (escapeCharIndex < 0) {
0139             break;
0140         }
0141         const QStringView sequence = QStringView(rc).mid(escapeCharIndex, 4);
0142         if (sequence.length() != 4 || sequence.at(1) != QLatin1Char('x')) {
0143             qWarning() << "Badly formed cgroup name" << name;
0144             return name;
0145         }
0146         bool ok;
0147         int character = sequence.mid(2).toInt(&ok, 16);
0148         if (ok) {
0149             rc.replace(escapeCharIndex, 4, QLatin1Char(character));
0150         }
0151     }
0152     return rc;
0153 }
0154 
0155 KService::Ptr CGroupPrivate::serviceFromAppId(const QString &processGroup)
0156 {
0157     const int lastSlash = processGroup.lastIndexOf(QLatin1Char('/'));
0158 
0159     QString serviceName = processGroup;
0160     if (lastSlash != -1) {
0161         serviceName = processGroup.mid(lastSlash + 1);
0162     }
0163 
0164     const QRegularExpressionMatch &appIdMatch = s_appIdFromProcessGroupPattern.match(serviceName);
0165 
0166     if (!appIdMatch.isValid() || !appIdMatch.hasMatch()) {
0167         // create a transient service object just to have a sensible name
0168         return KService::Ptr(new KService(serviceName, QString(), QString()));
0169     }
0170 
0171     const QString appId = unescapeName(appIdMatch.captured(2));
0172 
0173     KService::Ptr service = KService::serviceByMenuId(appId + QStringLiteral(".desktop"));
0174     if (!service && processGroup.endsWith(QLatin1String("@autostart.service"))) {
0175         auto file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("autostart/%1.desktop").arg(appId));
0176         if (!file.isEmpty()) {
0177             service = new KService(file);
0178         }
0179     }
0180     if (!service) {
0181         service = new KService(appId, QString(), QString());
0182     }
0183 
0184     return service;
0185 }
0186 
0187 QString CGroup::cgroupSysBasePath()
0188 {
0189     return s_cGroupSystemInformation->sysGgroupRoot;
0190 }
0191 
0192 CGroupSystemInformation::CGroupSystemInformation()
0193 {
0194     QDir base(QStringLiteral("/sys/fs/cgroup"));
0195     if (base.exists(QLatin1String("unified"))) {
0196         sysGgroupRoot = base.absoluteFilePath(QStringLiteral("unified"));
0197         return;
0198     }
0199     if (base.exists()) {
0200         sysGgroupRoot = base.absolutePath();
0201     }
0202 }