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(first: procname.cbegin(), last: procname.cend(), pred: [](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#ifdef Q_OS_OPENBSD
50 QStringLiteral("-ww"),
51 QStringLiteral("-x"),
52#endif
53 QStringLiteral("-e"),
54 QStringLiteral("-o"),
55#ifdef Q_OS_MAC
56 // command goes last, otherwise it is cut off
57 QStringLiteral("pid state user comm command"),
58#elif defined(Q_OS_OPENBSD)
59 // On OpenBSD "login" is user who started the process in difference to
60 // Linux where it is the effective user "ename" name.
61 QStringLiteral("pid,state,login,comm,args"),
62#else
63 QStringLiteral("pid,state,user,comm,cmd"),
64#endif
65 };
66 psProcess.start(QStringLiteral("ps"), arguments: args);
67 if (!psProcess.waitForStarted()) {
68 qCWarning(KCOREADDONS_DEBUG) << "Failed to execute ps" << args;
69 return rc;
70 }
71 psProcess.waitForFinished();
72 const QByteArray output = psProcess.readAllStandardOutput();
73 const QByteArray errorOutput = psProcess.readAllStandardError();
74 if (!errorOutput.isEmpty()) {
75 qCWarning(KCOREADDONS_DEBUG) << "ps said" << errorOutput;
76 }
77 // Split "457 S+ /Users/foo.app"
78 const QStringList lines = QString::fromLocal8Bit(ba: output).split(sep: QLatin1Char('\n'));
79 const int lineCount = lines.size();
80 const QChar blank = QLatin1Char(' ');
81 for (int l = 1; l < lineCount; l++) { // Skip header
82 const QString line = lines.at(i: l).simplified();
83 // we can't just split on blank as the process name might
84 // contain them
85 const int endOfPid = line.indexOf(c: blank);
86 const int endOfState = line.indexOf(c: blank, from: endOfPid + 1);
87 const int endOfUser = line.indexOf(c: blank, from: endOfState + 1);
88 const int endOfName = line.indexOf(c: blank, from: endOfUser + 1);
89
90 if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) {
91 const qint64 pid = QStringView(line).left(n: endOfPid).toUInt();
92
93 QString user = line.mid(position: endOfState + 1, n: endOfUser - endOfState - 1);
94 QString name = line.mid(position: endOfUser + 1, n: endOfName - endOfUser - 1);
95 QString command = line.right(n: line.size() - endOfName - 1);
96 rc.push_back(t: KProcessInfo(pid, command, name, user));
97 }
98 }
99
100 return rc;
101}
102
103bool getProcessInfo(const QString &procId, KProcessInfo &processInfo)
104{
105 if (!isUnixProcessId(procname: procId)) {
106 return false;
107 }
108 QString statusFileName(QStringLiteral("/stat"));
109 QString filename = QStringLiteral("/proc/");
110 filename += procId;
111 filename += statusFileName;
112 QFile file(filename);
113 if (!file.open(flags: QIODevice::ReadOnly)) {
114 return false; // process may have exited
115 }
116
117 const QStringList data = QString::fromLocal8Bit(ba: file.readAll()).split(sep: QLatin1Char(' '));
118 if (data.length() < 2) {
119 return false;
120 }
121 qint64 pid = procId.toUInt();
122 QString name = data.at(i: 1);
123 if (name.startsWith(c: QLatin1Char('(')) && name.endsWith(c: QLatin1Char(')'))) {
124 name.chop(n: 1);
125 name.remove(i: 0, len: 1);
126 }
127 // State is element 2
128 // PPID is element 3
129 QString user = QFileInfo(file).owner();
130 file.close();
131
132 QString command = name;
133
134 QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline"));
135 if (cmdFile.open(flags: QFile::ReadOnly)) {
136 QByteArray cmd = cmdFile.readAll();
137
138 if (!cmd.isEmpty()) {
139 // extract non-truncated name from cmdline
140 int zeroIndex = cmd.indexOf(c: '\0');
141 int processNameStart = cmd.lastIndexOf(c: '/', from: zeroIndex);
142 if (processNameStart == -1) {
143 processNameStart = 0;
144 } else {
145 processNameStart++;
146 }
147 name = QString::fromLocal8Bit(ba: cmd.mid(index: processNameStart, len: zeroIndex - processNameStart));
148
149 cmd.replace(before: '\0', after: ' ');
150 command = QString::fromLocal8Bit(ba: cmd).trimmed();
151 }
152 }
153 cmdFile.close();
154 processInfo = KProcessInfo(pid, command, name, user);
155 return true;
156}
157
158} // unnamed namespace
159
160// Determine UNIX processes by reading "/proc". Default to ps if
161// it does not exist
162KProcessInfoList KProcessList::processInfoList()
163{
164 const QDir procDir(QStringLiteral("/proc/"));
165 if (!procDir.exists()) {
166 return unixProcessListPS();
167 }
168 const QStringList procIds = procDir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot);
169 KProcessInfoList rc;
170 rc.reserve(asize: procIds.size());
171 for (const QString &procId : procIds) {
172 KProcessInfo processInfo;
173 if (getProcessInfo(procId, processInfo)) {
174 rc.push_back(t: processInfo);
175 }
176 }
177 return rc;
178}
179
180// Determine UNIX process by reading "/proc".
181//
182// TODO: Use ps if "/proc" does not exist or is bogus; use code
183// from unixProcessListPS() but add a `-p pid` argument.
184//
185KProcessInfo KProcessList::processInfo(qint64 pid)
186{
187 KProcessInfo processInfo;
188 getProcessInfo(procId: QString::number(pid), processInfo);
189 return processInfo;
190}
191

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