File indexing completed on 2024-05-19 03:56:25

0001 /*
0002     This file is part of the KDE Frameworks
0003 
0004     SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
0005     SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial
0008 */
0009 
0010 /*
0011  * Implementation notes:
0012  *
0013  * This file implements KProcessInfo and KProcessInfoList via Linux /proc
0014  * **or** via ps(1). If there's no /proc, it falls back to ps(1), usually.
0015  *
0016  * Although the code contains #ifdefs for FreeBSD (e.g. for ps(1) command-
0017  * line arguments), FreeBSD should never use this code, only the
0018  * procstat-based code in `kprocesslist_unix_procstat.cpp`.
0019  */
0020 
0021 #include "kcoreaddons_debug.h"
0022 #include "kprocesslist.h"
0023 
0024 #include <QDebug>
0025 #include <QDir>
0026 #include <QProcess>
0027 
0028 #ifdef Q_OS_FREEBSD
0029 #error This KProcessInfo implementation is not supported on FreeBSD (use procstat)
0030 #endif
0031 
0032 using namespace KProcessList;
0033 
0034 namespace
0035 {
0036 bool isUnixProcessId(const QString &procname)
0037 {
0038     return std::none_of(procname.cbegin(), procname.cend(), [](const QChar ch) {
0039         return !ch.isDigit();
0040     });
0041 }
0042 
0043 // Determine UNIX processes by running ps
0044 KProcessInfoList unixProcessListPS()
0045 {
0046     KProcessInfoList rc;
0047     QProcess psProcess;
0048     const QStringList args{
0049 #ifdef Q_OS_OPENBSD
0050         QStringLiteral("-ww"),
0051         QStringLiteral("-x"),
0052 #endif
0053         QStringLiteral("-e"),
0054         QStringLiteral("-o"),
0055 #ifdef Q_OS_MAC
0056         // command goes last, otherwise it is cut off
0057         QStringLiteral("pid state user comm command"),
0058 #elif defined(Q_OS_OPENBSD)
0059         // On OpenBSD "login" is user who started the process in difference to
0060         // Linux where it is the effective user "ename" name.
0061         QStringLiteral("pid,state,login,comm,args"),
0062 #else
0063         QStringLiteral("pid,state,user,comm,cmd"),
0064 #endif
0065     };
0066     psProcess.start(QStringLiteral("ps"), args);
0067     if (!psProcess.waitForStarted()) {
0068         qCWarning(KCOREADDONS_DEBUG) << "Failed to execute ps" << args;
0069         return rc;
0070     }
0071     psProcess.waitForFinished();
0072     const QByteArray output = psProcess.readAllStandardOutput();
0073     const QByteArray errorOutput = psProcess.readAllStandardError();
0074     if (!errorOutput.isEmpty()) {
0075         qCWarning(KCOREADDONS_DEBUG) << "ps said" << errorOutput;
0076     }
0077     // Split "457 S+   /Users/foo.app"
0078     const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n'));
0079     const int lineCount = lines.size();
0080     const QChar blank = QLatin1Char(' ');
0081     for (int l = 1; l < lineCount; l++) { // Skip header
0082         const QString line = lines.at(l).simplified();
0083         // we can't just split on blank as the process name might
0084         // contain them
0085         const int endOfPid = line.indexOf(blank);
0086         const int endOfState = line.indexOf(blank, endOfPid + 1);
0087         const int endOfUser = line.indexOf(blank, endOfState + 1);
0088         const int endOfName = line.indexOf(blank, endOfUser + 1);
0089 
0090         if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) {
0091             const qint64 pid = QStringView(line).left(endOfPid).toUInt();
0092 
0093             QString user = line.mid(endOfState + 1, endOfUser - endOfState - 1);
0094             QString name = line.mid(endOfUser + 1, endOfName - endOfUser - 1);
0095             QString command = line.right(line.size() - endOfName - 1);
0096             rc.push_back(KProcessInfo(pid, command, name, user));
0097         }
0098     }
0099 
0100     return rc;
0101 }
0102 
0103 bool getProcessInfo(const QString &procId, KProcessInfo &processInfo)
0104 {
0105     if (!isUnixProcessId(procId)) {
0106         return false;
0107     }
0108     QString statusFileName(QStringLiteral("/stat"));
0109     QString filename = QStringLiteral("/proc/");
0110     filename += procId;
0111     filename += statusFileName;
0112     QFile file(filename);
0113     if (!file.open(QIODevice::ReadOnly)) {
0114         return false; // process may have exited
0115     }
0116 
0117     const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' '));
0118     if (data.length() < 2) {
0119         return false;
0120     }
0121     qint64 pid = procId.toUInt();
0122     QString name = data.at(1);
0123     if (name.startsWith(QLatin1Char('(')) && name.endsWith(QLatin1Char(')'))) {
0124         name.chop(1);
0125         name.remove(0, 1);
0126     }
0127     // State is element 2
0128     // PPID is element 3
0129     QString user = QFileInfo(file).owner();
0130     file.close();
0131 
0132     QString command = name;
0133 
0134     QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline"));
0135     if (cmdFile.open(QFile::ReadOnly)) {
0136         QByteArray cmd = cmdFile.readAll();
0137 
0138         if (!cmd.isEmpty()) {
0139             // extract non-truncated name from cmdline
0140             int zeroIndex = cmd.indexOf('\0');
0141             int processNameStart = cmd.lastIndexOf('/', zeroIndex);
0142             if (processNameStart == -1) {
0143                 processNameStart = 0;
0144             } else {
0145                 processNameStart++;
0146             }
0147             name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart));
0148 
0149             cmd.replace('\0', ' ');
0150             command = QString::fromLocal8Bit(cmd).trimmed();
0151         }
0152     }
0153     cmdFile.close();
0154     processInfo = KProcessInfo(pid, command, name, user);
0155     return true;
0156 }
0157 
0158 } // unnamed namespace
0159 
0160 // Determine UNIX processes by reading "/proc". Default to ps if
0161 // it does not exist
0162 KProcessInfoList KProcessList::processInfoList()
0163 {
0164     const QDir procDir(QStringLiteral("/proc/"));
0165     if (!procDir.exists()) {
0166         return unixProcessListPS();
0167     }
0168     const QStringList procIds = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0169     KProcessInfoList rc;
0170     rc.reserve(procIds.size());
0171     for (const QString &procId : procIds) {
0172         KProcessInfo processInfo;
0173         if (getProcessInfo(procId, processInfo)) {
0174             rc.push_back(processInfo);
0175         }
0176     }
0177     return rc;
0178 }
0179 
0180 // Determine UNIX process by reading "/proc".
0181 //
0182 // TODO: Use ps if "/proc" does not exist or is bogus; use code
0183 //       from unixProcessListPS() but add a `-p pid` argument.
0184 //
0185 KProcessInfo KProcessList::processInfo(qint64 pid)
0186 {
0187     KProcessInfo processInfo;
0188     getProcessInfo(QString::number(pid), processInfo);
0189     return processInfo;
0190 }