KCoreAddons

kprocesslist_unix.cpp
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{
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"), 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(output).split(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(l).simplified();
83 // we can't just split on blank as the process name might
84 // contain them
85 const int endOfPid = line.indexOf(blank);
86 const int endOfState = line.indexOf(blank, endOfPid + 1);
87 const int endOfUser = line.indexOf(blank, endOfState + 1);
88 const int endOfName = line.indexOf(blank, endOfUser + 1);
89
90 if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) {
91 const qint64 pid = QStringView(line).left(endOfPid).toUInt();
92
93 QString user = line.mid(endOfState + 1, endOfUser - endOfState - 1);
94 QString name = line.mid(endOfUser + 1, endOfName - endOfUser - 1);
95 QString command = line.right(line.size() - endOfName - 1);
96 rc.push_back(KProcessInfo(pid, command, name, user));
97 }
98 }
99
100 return rc;
101}
102
103bool getProcessInfo(const QString &procId, KProcessInfo &processInfo)
104{
105 if (!isUnixProcessId(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(QIODevice::ReadOnly)) {
114 return false; // process may have exited
115 }
116
117 const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' '));
118 if (data.length() < 2) {
119 return false;
120 }
121 qint64 pid = procId.toUInt();
122 QString name = data.at(1);
123 if (name.startsWith(QLatin1Char('(')) && name.endsWith(QLatin1Char(')'))) {
124 name.chop(1);
125 name.remove(0, 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(QFile::ReadOnly)) {
136 QByteArray cmd = cmdFile.readAll();
137
138 if (!cmd.isEmpty()) {
139 // extract non-truncated name from cmdline
140 int zeroIndex = cmd.indexOf('\0');
141 int processNameStart = cmd.lastIndexOf('/', zeroIndex);
142 if (processNameStart == -1) {
143 processNameStart = 0;
144 } else {
145 processNameStart++;
146 }
147 name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart));
148
149 cmd.replace('\0', ' ');
150 command = QString::fromLocal8Bit(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(QDir::Dirs | QDir::NoDotAndDotDot);
170 rc.reserve(procIds.size());
171 for (const QString &procId : procIds) {
172 KProcessInfo processInfo;
173 if (getProcessInfo(procId, processInfo)) {
174 rc.push_back(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(QString::number(pid), processInfo);
189 return processInfo;
190}
Contains information about a process.
QString name(StandardAction id)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
qsizetype lastIndexOf(QByteArrayView bv) const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QString owner() const const
const_reference at(qsizetype i) const const
qsizetype length() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
QByteArray readAllStandardError()
QByteArray readAllStandardOutput()
void start(OpenMode mode)
bool waitForFinished(int msecs)
bool waitForStarted(int msecs)
const_iterator cbegin() const const
const_iterator cend() const const
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString right(qsizetype n) const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
uint toUInt(bool *ok, int base) const const
QString trimmed() const const
QStringView left(qsizetype length) const const
uint toUInt(bool *ok, int base) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:22 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.