// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2026 A-La-Karte Contributors #include "processscanner.h" #include #include #include 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 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 listPids() { QList 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::findByEnvironment(const QString &key, const QString &value) { QList matches; const QByteArray needle = (key + QLatin1Char('=') + value).toUtf8(); const QList 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::findByCmdline(const QString &substring) { QList matches; const QByteArray needle = substring.toUtf8(); const QList 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::findByExePath(const QString &dirPrefix) { QList matches; const QList 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()> matcher, int intervalMs, int timeoutMs) { cancel(); m_matcher = std::move(matcher); // Try once immediately const QList 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; }