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 | |
32 | using namespace KProcessList; |
33 | |
34 | namespace |
35 | { |
36 | bool 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 |
44 | KProcessInfoList 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 | |
103 | bool 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 |
162 | KProcessInfoList 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 | // |
185 | KProcessInfo KProcessList::processInfo(qint64 pid) |
186 | { |
187 | KProcessInfo processInfo; |
188 | getProcessInfo(procId: QString::number(pid), processInfo); |
189 | return processInfo; |
190 | } |
191 | |