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 <[email protected]>
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 "kprocesslist.h"
22 #include "kcoreaddons_debug.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 
32 using namespace KProcessList;
33 
34 namespace
35 {
36 bool isUnixProcessId(const QString &procname)
37 {
38  for (int i = 0; i != procname.size(); ++i) {
39  if (!procname.at(i).isDigit())
40  return false;
41  }
42  return true;
43 }
44 
45 // Determine UNIX processes by running ps
46 KProcessInfoList unixProcessListPS()
47 {
49  QProcess psProcess;
50  const QStringList args{
51  QStringLiteral("-e"),
52  QStringLiteral("-o"),
53 #ifdef Q_OS_MAC
54  // command goes last, otherwise it is cut off
55  QStringLiteral("pid state user comm command"),
56 #else
57 #ifdef Q_OS_FREEBSD
58  // "comm" is the bare command, e.g. "bash", "plasmashell", "ps"
59  // "args" is the command and arguments, e.g. "ps -e -o pid,state,user,comm,args"
60  //
61  // Keyword "cmd" is unknown, and "command" spits out the entire
62  // environment of the process as well.
63  QStringLiteral("pid,state,user,comm,args"),
64 #else
65  QStringLiteral("pid,state,user,comm,cmd"),
66 #endif
67 #endif
68  };
69  psProcess.start(QStringLiteral("ps"), args);
70  if (!psProcess.waitForStarted()) {
71  qCWarning(KCOREADDONS_DEBUG) << "Failed to execute ps" << args;
72  return rc;
73  }
74  psProcess.waitForFinished();
75  const QByteArray output = psProcess.readAllStandardOutput();
76  const QByteArray errorOutput = psProcess.readAllStandardError();
77  if (!errorOutput.isEmpty()) {
78  qCWarning(KCOREADDONS_DEBUG) << "ps said" << errorOutput;
79  }
80  // Split "457 S+ /Users/foo.app"
81  const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n'));
82  const int lineCount = lines.size();
83  const QChar blank = QLatin1Char(' ');
84  for (int l = 1; l < lineCount; l++) { // Skip header
85  const QString line = lines.at(l).simplified();
86  // we can't just split on blank as the process name might
87  // contain them
88  const int endOfPid = line.indexOf(blank);
89  const int endOfState = line.indexOf(blank, endOfPid + 1);
90  const int endOfUser = line.indexOf(blank, endOfState + 1);
91  const int endOfName = line.indexOf(blank, endOfUser + 1);
92 
93  if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) {
94  qint64 pid = line.leftRef(endOfPid).toUInt();
95  QString user = line.mid(endOfState + 1, endOfUser - endOfState - 1);
96  QString name = line.mid(endOfUser + 1, endOfName - endOfUser - 1);
97  QString command = line.right(line.size() - endOfName - 1);
98  rc.push_back(KProcessInfo(pid, command, name, user));
99  }
100  }
101 
102  return rc;
103 }
104 
105 bool getProcessInfo(const QString &procId, KProcessInfo &processInfo)
106 {
107  if (!isUnixProcessId(procId))
108  return false;
109 #ifdef Q_OS_FREEBSD
110  QString statusFileName(QStringLiteral("/status"));
111 #else
112  QString statusFileName(QStringLiteral("/stat"));
113 #endif
114  QString filename = QStringLiteral("/proc/");
115  filename += procId;
116  filename += statusFileName;
117  QFile file(filename);
118  if (!file.open(QIODevice::ReadOnly))
119  return false; // process may have exited
120 
121  const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' '));
122  if (data.length() < 2) {
123  return false;
124  }
125  qint64 pid = procId.toUInt();
126  QString name = data.at(1);
127  if (name.startsWith(QLatin1Char('(')) && name.endsWith(QLatin1Char(')'))) {
128  name.chop(1);
129  name.remove(0, 1);
130  }
131  // State is element 2
132  // PPID is element 3
133  QString user = QFileInfo(file).owner();
134  file.close();
135 
136  QString command = name;
137 
138  QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline"));
139  if (cmdFile.open(QFile::ReadOnly)) {
140  QByteArray cmd = cmdFile.readAll();
141 
142  if (!cmd.isEmpty()) {
143  // extract non-truncated name from cmdline
144  int zeroIndex = cmd.indexOf('\0');
145  int processNameStart = cmd.lastIndexOf('/', zeroIndex);
146  if (processNameStart == -1) {
147  processNameStart = 0;
148  } else {
149  processNameStart++;
150  }
151  name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart));
152 
153  cmd.replace('\0', ' ');
154  command = QString::fromLocal8Bit(cmd).trimmed();
155  }
156  }
157  cmdFile.close();
158  processInfo = KProcessInfo(pid, command, name, user);
159  return true;
160 }
161 
162 } // unnamed namespace
163 
164 // Determine UNIX processes by reading "/proc". Default to ps if
165 // it does not exist
166 KProcessInfoList KProcessList::processInfoList()
167 {
168  const QDir procDir(QStringLiteral("/proc/"));
169  if (!procDir.exists())
170  return unixProcessListPS();
171  const QStringList procIds = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
172 #ifdef Q_OS_FREEBSD
173  if (procIds.isEmpty())
174  return unixProcessListPS();
175 #endif
176  KProcessInfoList rc;
177  rc.reserve(procIds.size());
178  for (const QString &procId : procIds) {
179  KProcessInfo processInfo;
180  if (getProcessInfo(procId, processInfo)) {
181  rc.push_back(processInfo);
182  }
183  }
184  return rc;
185 }
186 
187 // Determine UNIX process by reading "/proc".
188 //
189 // TODO: Use ps if "/proc" does not exist or is bogus; use code
190 // from unixProcessListPS() but add a `-p pid` argument.
191 //
192 KProcessInfo KProcessList::processInfo(qint64 pid)
193 {
194  KProcessInfo processInfo;
195  getProcessInfo(QString::number(pid), processInfo);
196  return processInfo;
197 }
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
Contains information about a process.
Definition: kprocesslist.h:27
QString name(const QVariant &location)
void push_back(const T &value)
int length() const const
int lastIndexOf(char ch, int from) const const
void reserve(int alloc)
bool isDigit() const const
const T & at(int i) const const
int size() const const
bool isEmpty() const const
QString & remove(int position, int n)
void chop(int n)
int size() const const
int indexOf(char ch, int from) const const
QString number(int n, int base)
QString fromLocal8Bit(const char *str, int size)
QStringRef leftRef(int n) const const
bool isEmpty() const const
QString trimmed() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray & replace(int pos, int len, const char *after)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray mid(int pos, int len) const const
QString right(int n) const const
bool waitForStarted(int msecs)
QString owner() const const
uint toUInt(bool *ok, int base) const const
QString mid(int position, int n) const const
const QChar at(int position) const const
QByteArray readAllStandardOutput()
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QByteArray readAllStandardError()
uint toUInt(bool *ok, int base) const const
bool waitForFinished(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Apr 18 2021 23:02:02 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.