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(procname.cbegin(), procname.cend(), [](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 | 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 | |
95 | bool 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 |
154 | KProcessInfoList 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 | // |
177 | KProcessInfo KProcessList::processInfo(qint64 pid) |
178 | { |
179 | KProcessInfo processInfo; |
180 | getProcessInfo(QString::number(pid), processInfo); |
181 | return processInfo; |
182 | } |
183 | |