File indexing completed on 2024-04-28 16:49:51
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 QVector<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(QVector<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 QVector<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(QVector<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 QVector<pid_t> CGroup::pids() const 0116 { 0117 return d->pids; 0118 } 0119 0120 void CGroup::setPids(const QVector<pid_t> &pids) 0121 { 0122 d->pids = pids; 0123 } 0124 0125 void CGroup::requestPids(QObject *context, std::function<void(QVector<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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0142 const QStringRef sequence = rc.midRef(escapeCharIndex, 4); 0143 #else 0144 const QStringView sequence = rc.mid(escapeCharIndex, 4); 0145 #endif 0146 if (sequence.length() != 4 || sequence.at(1) != QLatin1Char('x')) { 0147 qWarning() << "Badly formed cgroup name" << name; 0148 return name; 0149 } 0150 bool ok; 0151 int character = sequence.mid(2).toInt(&ok, 16); 0152 if (ok) { 0153 rc.replace(escapeCharIndex, 4, QLatin1Char(character)); 0154 } 0155 } 0156 return rc; 0157 } 0158 0159 KService::Ptr CGroupPrivate::serviceFromAppId(const QString &processGroup) 0160 { 0161 const int lastSlash = processGroup.lastIndexOf(QLatin1Char('/')); 0162 0163 QString serviceName = processGroup; 0164 if (lastSlash != -1) { 0165 serviceName = processGroup.mid(lastSlash + 1); 0166 } 0167 0168 const QRegularExpressionMatch &appIdMatch = s_appIdFromProcessGroupPattern.match(serviceName); 0169 0170 if (!appIdMatch.isValid() || !appIdMatch.hasMatch()) { 0171 // create a transient service object just to have a sensible name 0172 return KService::Ptr(new KService(serviceName, QString(), QString())); 0173 } 0174 0175 const QString appId = unescapeName(appIdMatch.captured(2)); 0176 0177 KService::Ptr service = KService::serviceByMenuId(appId + QStringLiteral(".desktop")); 0178 if (!service && processGroup.endsWith(QLatin1String("@autostart.service"))) { 0179 auto file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("autostart/%1.desktop").arg(appId)); 0180 if (!file.isEmpty()) { 0181 service = new KService(file); 0182 } 0183 } 0184 if (!service) { 0185 service = new KService(appId, QString(), QString()); 0186 } 0187 0188 return service; 0189 } 0190 0191 QString CGroup::cgroupSysBasePath() 0192 { 0193 return s_cGroupSystemInformation->sysGgroupRoot; 0194 } 0195 0196 CGroupSystemInformation::CGroupSystemInformation() 0197 { 0198 QDir base(QStringLiteral("/sys/fs/cgroup")); 0199 if (base.exists(QLatin1String("unified"))) { 0200 sysGgroupRoot = base.absoluteFilePath(QStringLiteral("unified")); 0201 return; 0202 } 0203 if (base.exists()) { 0204 sysGgroupRoot = base.absolutePath(); 0205 } 0206 }