1/*
2 This file is part of the KDE Frameworks
3
4 SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
5 SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
6
7 SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial
8*/
9
10/*
11 * Implementation notes:
12 *
13 * This file implements KProcessInfo and KProcessInfoList via Linux /proc
14 * **or** via ps(1). If there's no /proc, it falls back to ps(1), usually.
15 *
16 * Although the code contains #ifdefs for FreeBSD (e.g. for ps(1) command-
17 * line arguments), FreeBSD should never use this code, only the
18 * procstat-based code in `kprocesslist_unix_procstat.cpp`.
19 */
20
21#include "kcoreaddons_debug.h"
22#include "kprocesslist.h"
23
24#include <QDebug>
25#include <QDir>
26#include <QProcess>
27
28#ifdef Q_OS_FREEBSD
29#error This KProcessInfo implementation is not supported on FreeBSD (use procstat)
30#endif
31
32using namespace KProcessList;
33
34namespace
35{
36bool isUnixProcessId(const QString &procname)
37{
38 return std::none_of(procname.cbegin(), procname.cend(), [](const QChar ch) {
39 return !ch.isDigit();
40 });
41}
42
43// Determine UNIX processes by running ps
44KProcessInfoList unixProcessListPS()
45{
46 KProcessInfoList rc;
47 QProcess psProcess;
48 const QStringList args{
49 QStringLiteral("-e"),
50 QStringLiteral("-o"),
51#ifdef Q_OS_MAC
52 // command goes last, otherwise it is cut off
53 QStringLiteral("pid state user comm command"),
54#else
55 QStringLiteral("pid,state,user,comm,cmd"),
56#endif
57 };
58 psProcess.start(QStringLiteral("ps"), args);
59 if (!psProcess.waitForStarted()) {
60 qCWarning(KCOREADDONS_DEBUG) << "Failed to execute ps" << args;
61 return rc;
62 }
63 psProcess.waitForFinished();
64 const QByteArray output = psProcess.readAllStandardOutput();
65 const QByteArray errorOutput = psProcess.readAllStandardError();
66 if (!errorOutput.isEmpty()) {
67 qCWarning(KCOREADDONS_DEBUG) << "ps said" << errorOutput;
68 }
69 // Split "457 S+ /Users/foo.app"
70 const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n'));
71 const int lineCount = lines.size();
72 const QChar blank = QLatin1Char(' ');
73 for (int l = 1; l < lineCount; l++) { // Skip header
74 const QString line = lines.at(l).simplified();
75 // we can't just split on blank as the process name might
76 // contain them
77 const int endOfPid = line.indexOf(blank);
78 const int endOfState = line.indexOf(blank, endOfPid + 1);
79 const int endOfUser = line.indexOf(blank, endOfState + 1);
80 const int endOfName = line.indexOf(blank, endOfUser + 1);
81
82 if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) {
83 const qint64 pid = QStringView(line).left(endOfPid).toUInt();
84
85 QString user = line.mid(endOfState + 1, endOfUser - endOfState - 1);
86 QString name = line.mid(endOfUser + 1, endOfName - endOfUser - 1);
87 QString command = line.right(line.size() - endOfName - 1);
88 rc.push_back(KProcessInfo(pid, command, name, user));
89 }
90 }
91
92 return rc;
93}
94
95bool getProcessInfo(const QString &procId, KProcessInfo &processInfo)
96{
97 if (!isUnixProcessId(procId)) {
98 return false;
99 }
100 QString statusFileName(QStringLiteral("/stat"));
101 QString filename = QStringLiteral("/proc/");
102 filename += procId;
103 filename += statusFileName;
104 QFile file(filename);
105 if (!file.open(QIODevice::ReadOnly)) {
106 return false; // process may have exited
107 }
108
109 const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' '));
110 if (data.length() < 2) {
111 return false;
112 }
113 qint64 pid = procId.toUInt();
114 QString name = data.at(1);
115 if (name.startsWith(QLatin1Char('(')) && name.endsWith(QLatin1Char(')'))) {
116 name.chop(1);
117 name.remove(0, 1);
118 }
119 // State is element 2
120 // PPID is element 3
121 QString user = QFileInfo(file).owner();
122 file.close();
123
124 QString command = name;
125
126 QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline"));
127 if (cmdFile.open(QFile::ReadOnly)) {
128 QByteArray cmd = cmdFile.readAll();
129
130 if (!cmd.isEmpty()) {
131 // extract non-truncated name from cmdline
132 int zeroIndex = cmd.indexOf('\0');
133 int processNameStart = cmd.lastIndexOf('/', zeroIndex);
134 if (processNameStart == -1) {
135 processNameStart = 0;
136 } else {
137 processNameStart++;
138 }
139 name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart));
140
141 cmd.replace('\0', ' ');
142 command = QString::fromLocal8Bit(cmd).trimmed();
143 }
144 }
145 cmdFile.close();
146 processInfo = KProcessInfo(pid, command, name, user);
147 return true;
148}
149
150} // unnamed namespace
151
152// Determine UNIX processes by reading "/proc". Default to ps if
153// it does not exist
154KProcessInfoList KProcessList::processInfoList()
155{
156 const QDir procDir(QStringLiteral("/proc/"));
157 if (!procDir.exists()) {
158 return unixProcessListPS();
159 }
160 const QStringList procIds = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
161 KProcessInfoList rc;
162 rc.reserve(procIds.size());
163 for (const QString &procId : procIds) {
164 KProcessInfo processInfo;
165 if (getProcessInfo(procId, processInfo)) {
166 rc.push_back(processInfo);
167 }
168 }
169 return rc;
170}
171
172// Determine UNIX process by reading "/proc".
173//
174// TODO: Use ps if "/proc" does not exist or is bogus; use code
175// from unixProcessListPS() but add a `-p pid` argument.
176//
177KProcessInfo KProcessList::processInfo(qint64 pid)
178{
179 KProcessInfo processInfo;
180 getProcessInfo(QString::number(pid), processInfo);
181 return processInfo;
182}
183

source code of kcoreaddons/src/lib/util/kprocesslist_unix.cpp