cgroup.cpp 6.11 KB
Newer Older
1
2
/*
    Copyright (c) 2019 David Edmundson <davidedmundson@kde.org>
3
    Copyright (c) 2020 David Redondo <kde@david-redondo.de>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "cgroup.h"

#include <QDebug>
David Edmundson's avatar
David Edmundson committed
24
25
#include <QDir>
#include <QFile>
26
27
#include <QRegularExpression>
#include <QRegularExpressionMatch>
David Edmundson's avatar
David Edmundson committed
28
#include <QStringView>
29
#include <QThreadPool>
30
31
32
33
34

#include "process.h"

using namespace KSysGuard;

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
class FunctionRunnable : public QRunnable
{
    std::function<void()> m_functionToRun;
public:
    FunctionRunnable(std::function<void()> functionToRun) : m_functionToRun(std::move(functionToRun))
    {
    }
    void run() override
    {
        m_functionToRun();
    }
};
#endif

50
51
52
53
54
55
56
57
58
59
class KSysGuard::CGroupPrivate
{
public:
    CGroupPrivate(const QString &_processGroupId)
        : processGroupId(_processGroupId)
        , service(serviceFromAppId(_processGroupId))
    {
    }
    const QString processGroupId;
    const KService::Ptr service;
60
61
62
    QVector<pid_t> pids;
    std::mutex pidsLock;

63
64
65
    static KService::Ptr serviceFromAppId(const QString &appId);

    static QRegularExpression s_appIdFromProcessGroupPattern;
David Edmundson's avatar
David Edmundson committed
66
    static QString unescapeName(const QString &cgroupId);
67
68
69
70
71
72
73
74
75
76
77
};

class CGroupSystemInformation
{
public:
    CGroupSystemInformation();
    QString sysGgroupRoot;
};

Q_GLOBAL_STATIC(CGroupSystemInformation, s_cGroupSystemInformation)

78
79
80
// The spec says that the two following schemes are allowed
// - app[-<launcher>]-<ApplicationID>-<RANDOM>.scope
// - app[-<launcher>]-<ApplicationID>[@<RANDOM>].service
81
82
83
// Flatpak's are currently in a cgroup, but they don't follow the specification
// this has been fixed, but this provides some compatability till that lands
// app vs apps exists because the spec changed.
84
QRegularExpression CGroupPrivate::s_appIdFromProcessGroupPattern(QStringLiteral("[app|apps|flatpak]-(?:[^-]*-)?([^-]+(?=-.*\\.scope)|[^@]+(?=(?:@.*)?\\.service))"));
85
86
87
88
89
90
91
92

CGroup::CGroup(const QString &id)
    : d(new CGroupPrivate(id))
{
}

CGroup::~CGroup()
{
93
    std::lock_guard<std::mutex>{d->pidsLock};
94
95
96
97
98
99
100
101
102
103
104
105
}

QString KSysGuard::CGroup::id() const
{
    return d->processGroupId;
}

KService::Ptr KSysGuard::CGroup::service() const
{
    return d->service;
}

106
QVector<pid_t> CGroup::pids() const
107
{
108
109
    std::lock_guard<std::mutex> lock{d->pidsLock};
    return d->pids;
110
111
}

112
void CGroup::setPids(const QVector<pid_t>& pids)
113
{
114
115
    std::lock_guard<std::mutex> lock{d->pidsLock};
    d->pids = pids;
116
117
}

118
void CGroup::requestPids(QPointer<QObject> context, std::function<void()> callback)
119
{
120
    QString path = cgroupSysBasePath() + d->processGroupId + QLatin1String("/cgroup.procs");
121
    auto runnable = [this, path, callback, context]() {
122
123
124
125
126
127
128
129
130
131
        QFile pidFile(path);
        pidFile.open(QFile::ReadOnly | QIODevice::Text);
        QTextStream stream(&pidFile);

        QVector<pid_t> pids;
        QString line = stream.readLine();
        while (!line.isNull()) {
            pids.append(line.toLong());
            line = stream.readLine();
        }
132
        setPids(pids);
133

134
135
136
137
        // Ensure we call the callback on the thread the context object lives on.
        if (context) {
            QMetaObject::invokeMethod(context, callback);
        }
138
    };
139
140

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
141
    QThreadPool::globalInstance()->start(runnable);
142
143
144
#else
    QThreadPool::globalInstance()->start(new FunctionRunnable(std::move(runnable)));
#endif
145
146
}

David Edmundson's avatar
David Edmundson committed
147
148
149
150
151
152
153
154
QString CGroupPrivate::unescapeName(const QString &name) {
    // strings are escaped in the form of \xZZ where ZZ is a two digits in hex representing an ascii code
    QString rc = name;
    while (true) {
        int escapeCharIndex = rc.indexOf(QLatin1Char('\\'));
        if (escapeCharIndex < 0) {
            break;
        }
155
        const QStringRef sequence = rc.midRef(escapeCharIndex, 4);
David Edmundson's avatar
David Edmundson committed
156
157
158
159
160
        if (sequence.length() != 4 || sequence.at(1) != QLatin1Char('x')) {
            qWarning() << "Badly formed cgroup name" << name;
            return name;
        }
        bool ok;
161
        int character = sequence.mid(2).toInt(&ok, 16);
David Edmundson's avatar
David Edmundson committed
162
163
164
165
166
167
168
169
170
        if (ok) {
            rc.replace(escapeCharIndex, 4, QLatin1Char(character));
        }
    }
    return rc;
}



171
172
173
174
175
176
177
178
KService::Ptr CGroupPrivate::serviceFromAppId(const QString &processGroup)
{
    const int lastSlash = processGroup.lastIndexOf(QLatin1Char('/'));

    QString serviceName = processGroup;
    if (lastSlash != -1) {
        serviceName = processGroup.mid(lastSlash + 1);
    }
David Edmundson's avatar
David Edmundson committed
179
    serviceName = unescapeName(serviceName);
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215

    const QRegularExpressionMatch &appIdMatch = s_appIdFromProcessGroupPattern.match(serviceName);

    if (!appIdMatch.isValid() || !appIdMatch.hasMatch()) {
        // create a transient service object just to have a sensible name
        return KService::Ptr(new KService(serviceName, QString(), QString()));
    }

    const QString appId = appIdMatch.captured(1);

    KService::Ptr service = KService::serviceByMenuId(appId + QStringLiteral(".desktop"));
    if (!service) {
        service = new KService(appId, QString(), QString());
    }

    return service;
}

QString CGroup::cgroupSysBasePath()
{
    return s_cGroupSystemInformation->sysGgroupRoot;
}

CGroupSystemInformation::CGroupSystemInformation()
{
    QDir base(QStringLiteral("/sys/fs/cgroup"));
    if (base.exists(QLatin1String("unified"))) {
        sysGgroupRoot = base.absoluteFilePath(QStringLiteral("unified"));
        return;
    }
    if (base.exists()) {
        sysGgroupRoot = base.absolutePath();
    }
}