mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
184 lines
4.8 KiB
C++
184 lines
4.8 KiB
C++
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||
|
|
|
||
|
|
#include "processscanner.h"
|
||
|
|
|
||
|
|
#include <QDir>
|
||
|
|
#include <QFile>
|
||
|
|
#include <QFileInfo>
|
||
|
|
|
||
|
|
ProcessScanner::ProcessScanner(QObject *parent)
|
||
|
|
: QObject(parent)
|
||
|
|
{
|
||
|
|
m_timer.setSingleShot(false);
|
||
|
|
m_deadline.setSingleShot(true);
|
||
|
|
|
||
|
|
connect(&m_deadline, &QTimer::timeout, this, [this]() {
|
||
|
|
m_timer.stop();
|
||
|
|
Q_EMIT timedOut();
|
||
|
|
});
|
||
|
|
|
||
|
|
connect(&m_timer, &QTimer::timeout, this, [this]() {
|
||
|
|
if (!m_matcher) {
|
||
|
|
m_timer.stop();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const QList<Match> results = m_matcher();
|
||
|
|
if (!results.isEmpty()) {
|
||
|
|
m_timer.stop();
|
||
|
|
m_deadline.stop();
|
||
|
|
Q_EMIT found(results);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
static QByteArray readProcFile(const QString &path, qint64 maxSize = 65536)
|
||
|
|
{
|
||
|
|
QFile f(path);
|
||
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
return f.read(maxSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
static QString readExeLink(const QString &pidDir)
|
||
|
|
{
|
||
|
|
return QFileInfo(pidDir + QStringLiteral("/exe")).symLinkTarget();
|
||
|
|
}
|
||
|
|
|
||
|
|
static QByteArray readCmdline(const QString &pidDir)
|
||
|
|
{
|
||
|
|
return readProcFile(pidDir + QStringLiteral("/cmdline"));
|
||
|
|
}
|
||
|
|
|
||
|
|
static QByteArray readEnviron(const QString &pidDir)
|
||
|
|
{
|
||
|
|
return readProcFile(pidDir + QStringLiteral("/environ"));
|
||
|
|
}
|
||
|
|
|
||
|
|
static QList<uint> listPids()
|
||
|
|
{
|
||
|
|
QList<uint> pids;
|
||
|
|
const QDir proc(QStringLiteral("/proc"));
|
||
|
|
const QStringList entries = proc.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||
|
|
for (const QString &entry : entries) {
|
||
|
|
bool ok = false;
|
||
|
|
const uint pid = entry.toUInt(&ok);
|
||
|
|
if (ok && pid > 1) {
|
||
|
|
pids.push_back(pid);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return pids;
|
||
|
|
}
|
||
|
|
|
||
|
|
QList<ProcessScanner::Match> ProcessScanner::findByEnvironment(const QString &key, const QString &value)
|
||
|
|
{
|
||
|
|
QList<Match> matches;
|
||
|
|
const QByteArray needle = (key + QLatin1Char('=') + value).toUtf8();
|
||
|
|
|
||
|
|
const QList<uint> pids = listPids();
|
||
|
|
for (uint pid : pids) {
|
||
|
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||
|
|
const QByteArray env = readEnviron(pidDir);
|
||
|
|
if (env.isEmpty()) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// environ entries are separated by null bytes
|
||
|
|
bool found = false;
|
||
|
|
int start = 0;
|
||
|
|
while (start < env.size()) {
|
||
|
|
int end = env.indexOf('\0', start);
|
||
|
|
if (end < 0) {
|
||
|
|
end = env.size();
|
||
|
|
}
|
||
|
|
if (env.mid(start, end - start) == needle) {
|
||
|
|
found = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
start = end + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (found) {
|
||
|
|
Match m;
|
||
|
|
m.pid = pid;
|
||
|
|
m.exe = readExeLink(pidDir);
|
||
|
|
const QByteArray cmd = readCmdline(pidDir);
|
||
|
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||
|
|
matches.push_back(m);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return matches;
|
||
|
|
}
|
||
|
|
|
||
|
|
QList<ProcessScanner::Match> ProcessScanner::findByCmdline(const QString &substring)
|
||
|
|
{
|
||
|
|
QList<Match> matches;
|
||
|
|
const QByteArray needle = substring.toUtf8();
|
||
|
|
|
||
|
|
const QList<uint> pids = listPids();
|
||
|
|
for (uint pid : pids) {
|
||
|
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||
|
|
const QByteArray cmd = readCmdline(pidDir);
|
||
|
|
if (cmd.isEmpty()) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// cmdline has null-separated args; search the whole blob
|
||
|
|
if (cmd.contains(needle)) {
|
||
|
|
Match m;
|
||
|
|
m.pid = pid;
|
||
|
|
m.exe = readExeLink(pidDir);
|
||
|
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||
|
|
matches.push_back(m);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return matches;
|
||
|
|
}
|
||
|
|
|
||
|
|
QList<ProcessScanner::Match> ProcessScanner::findByExePath(const QString &dirPrefix)
|
||
|
|
{
|
||
|
|
QList<Match> matches;
|
||
|
|
|
||
|
|
const QList<uint> pids = listPids();
|
||
|
|
for (uint pid : pids) {
|
||
|
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||
|
|
const QString exe = readExeLink(pidDir);
|
||
|
|
if (exe.isEmpty()) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (exe.startsWith(dirPrefix)) {
|
||
|
|
Match m;
|
||
|
|
m.pid = pid;
|
||
|
|
m.exe = exe;
|
||
|
|
const QByteArray cmd = readCmdline(pidDir);
|
||
|
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||
|
|
matches.push_back(m);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return matches;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ProcessScanner::pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs, int timeoutMs)
|
||
|
|
{
|
||
|
|
cancel();
|
||
|
|
m_matcher = std::move(matcher);
|
||
|
|
|
||
|
|
// Try once immediately
|
||
|
|
const QList<Match> immediate = m_matcher();
|
||
|
|
if (!immediate.isEmpty()) {
|
||
|
|
Q_EMIT found(immediate);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_deadline.start(timeoutMs);
|
||
|
|
m_timer.start(intervalMs);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ProcessScanner::cancel()
|
||
|
|
{
|
||
|
|
m_timer.stop();
|
||
|
|
m_deadline.stop();
|
||
|
|
m_matcher = nullptr;
|
||
|
|
}
|