File indexing completed on 2024-05-19 03:56:25
0001 /* 0002 This file is part of the KDE Frameworks 0003 0004 SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies). 0005 SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk> 0006 0007 SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial 0008 */ 0009 0010 /* 0011 * Implementation notes: 0012 * 0013 * This file implements KProcessInfo and KProcessInfoList via Linux /proc 0014 * **or** via ps(1). If there's no /proc, it falls back to ps(1), usually. 0015 * 0016 * Although the code contains #ifdefs for FreeBSD (e.g. for ps(1) command- 0017 * line arguments), FreeBSD should never use this code, only the 0018 * procstat-based code in `kprocesslist_unix_procstat.cpp`. 0019 */ 0020 0021 #include "kcoreaddons_debug.h" 0022 #include "kprocesslist.h" 0023 0024 #include <QDebug> 0025 #include <QDir> 0026 #include <QProcess> 0027 0028 #ifdef Q_OS_FREEBSD 0029 #error This KProcessInfo implementation is not supported on FreeBSD (use procstat) 0030 #endif 0031 0032 using namespace KProcessList; 0033 0034 namespace 0035 { 0036 bool isUnixProcessId(const QString &procname) 0037 { 0038 return std::none_of(procname.cbegin(), procname.cend(), [](const QChar ch) { 0039 return !ch.isDigit(); 0040 }); 0041 } 0042 0043 // Determine UNIX processes by running ps 0044 KProcessInfoList unixProcessListPS() 0045 { 0046 KProcessInfoList rc; 0047 QProcess psProcess; 0048 const QStringList args{ 0049 #ifdef Q_OS_OPENBSD 0050 QStringLiteral("-ww"), 0051 QStringLiteral("-x"), 0052 #endif 0053 QStringLiteral("-e"), 0054 QStringLiteral("-o"), 0055 #ifdef Q_OS_MAC 0056 // command goes last, otherwise it is cut off 0057 QStringLiteral("pid state user comm command"), 0058 #elif defined(Q_OS_OPENBSD) 0059 // On OpenBSD "login" is user who started the process in difference to 0060 // Linux where it is the effective user "ename" name. 0061 QStringLiteral("pid,state,login,comm,args"), 0062 #else 0063 QStringLiteral("pid,state,user,comm,cmd"), 0064 #endif 0065 }; 0066 psProcess.start(QStringLiteral("ps"), args); 0067 if (!psProcess.waitForStarted()) { 0068 qCWarning(KCOREADDONS_DEBUG) << "Failed to execute ps" << args; 0069 return rc; 0070 } 0071 psProcess.waitForFinished(); 0072 const QByteArray output = psProcess.readAllStandardOutput(); 0073 const QByteArray errorOutput = psProcess.readAllStandardError(); 0074 if (!errorOutput.isEmpty()) { 0075 qCWarning(KCOREADDONS_DEBUG) << "ps said" << errorOutput; 0076 } 0077 // Split "457 S+ /Users/foo.app" 0078 const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n')); 0079 const int lineCount = lines.size(); 0080 const QChar blank = QLatin1Char(' '); 0081 for (int l = 1; l < lineCount; l++) { // Skip header 0082 const QString line = lines.at(l).simplified(); 0083 // we can't just split on blank as the process name might 0084 // contain them 0085 const int endOfPid = line.indexOf(blank); 0086 const int endOfState = line.indexOf(blank, endOfPid + 1); 0087 const int endOfUser = line.indexOf(blank, endOfState + 1); 0088 const int endOfName = line.indexOf(blank, endOfUser + 1); 0089 0090 if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) { 0091 const qint64 pid = QStringView(line).left(endOfPid).toUInt(); 0092 0093 QString user = line.mid(endOfState + 1, endOfUser - endOfState - 1); 0094 QString name = line.mid(endOfUser + 1, endOfName - endOfUser - 1); 0095 QString command = line.right(line.size() - endOfName - 1); 0096 rc.push_back(KProcessInfo(pid, command, name, user)); 0097 } 0098 } 0099 0100 return rc; 0101 } 0102 0103 bool getProcessInfo(const QString &procId, KProcessInfo &processInfo) 0104 { 0105 if (!isUnixProcessId(procId)) { 0106 return false; 0107 } 0108 QString statusFileName(QStringLiteral("/stat")); 0109 QString filename = QStringLiteral("/proc/"); 0110 filename += procId; 0111 filename += statusFileName; 0112 QFile file(filename); 0113 if (!file.open(QIODevice::ReadOnly)) { 0114 return false; // process may have exited 0115 } 0116 0117 const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' ')); 0118 if (data.length() < 2) { 0119 return false; 0120 } 0121 qint64 pid = procId.toUInt(); 0122 QString name = data.at(1); 0123 if (name.startsWith(QLatin1Char('(')) && name.endsWith(QLatin1Char(')'))) { 0124 name.chop(1); 0125 name.remove(0, 1); 0126 } 0127 // State is element 2 0128 // PPID is element 3 0129 QString user = QFileInfo(file).owner(); 0130 file.close(); 0131 0132 QString command = name; 0133 0134 QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline")); 0135 if (cmdFile.open(QFile::ReadOnly)) { 0136 QByteArray cmd = cmdFile.readAll(); 0137 0138 if (!cmd.isEmpty()) { 0139 // extract non-truncated name from cmdline 0140 int zeroIndex = cmd.indexOf('\0'); 0141 int processNameStart = cmd.lastIndexOf('/', zeroIndex); 0142 if (processNameStart == -1) { 0143 processNameStart = 0; 0144 } else { 0145 processNameStart++; 0146 } 0147 name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart)); 0148 0149 cmd.replace('\0', ' '); 0150 command = QString::fromLocal8Bit(cmd).trimmed(); 0151 } 0152 } 0153 cmdFile.close(); 0154 processInfo = KProcessInfo(pid, command, name, user); 0155 return true; 0156 } 0157 0158 } // unnamed namespace 0159 0160 // Determine UNIX processes by reading "/proc". Default to ps if 0161 // it does not exist 0162 KProcessInfoList KProcessList::processInfoList() 0163 { 0164 const QDir procDir(QStringLiteral("/proc/")); 0165 if (!procDir.exists()) { 0166 return unixProcessListPS(); 0167 } 0168 const QStringList procIds = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); 0169 KProcessInfoList rc; 0170 rc.reserve(procIds.size()); 0171 for (const QString &procId : procIds) { 0172 KProcessInfo processInfo; 0173 if (getProcessInfo(procId, processInfo)) { 0174 rc.push_back(processInfo); 0175 } 0176 } 0177 return rc; 0178 } 0179 0180 // Determine UNIX process by reading "/proc". 0181 // 0182 // TODO: Use ps if "/proc" does not exist or is bogus; use code 0183 // from unixProcessListPS() but add a `-p pid` argument. 0184 // 0185 KProcessInfo KProcessList::processInfo(qint64 pid) 0186 { 0187 KProcessInfo processInfo; 0188 getProcessInfo(QString::number(pid), processInfo); 0189 return processInfo; 0190 }