diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -45,6 +45,7 @@ set(kcoreaddons_OPTIONAL_SRCS ${kcoreaddons_OPTIONAL_SRCS} text/kmacroexpander_win.cpp + util/kprocesslist_win.cpp util/kshell_win.cpp util/kuser_win.cpp ) @@ -54,6 +55,7 @@ set(kcoreaddons_OPTIONAL_SRCS ${kcoreaddons_OPTIONAL_SRCS} text/kmacroexpander_unix.cpp + util/kprocesslist_unix.cpp util/kuser_unix.cpp util/kshell_unix.cpp ) diff --git a/src/lib/util/kprocesslist.h b/src/lib/util/kprocesslist.h new file mode 100644 --- /dev/null +++ b/src/lib/util/kprocesslist.h @@ -0,0 +1,58 @@ +/************************************************************************** +** +** This file is part of the KDE Frameworks +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2019 David Hallas +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef KPROCESSLIST_H +#define KPROCESSLIST_H + +#include +#include + +/** + * @brief Contains information about a process + * @since 5.57 + */ +struct KProcessInfo { + unsigned int pid; ///< The pid of the process + QString name; ///< The name of the process - this is not the full path to the executable + QString image; ///< What is this? + QString state; ///< Running state - should we have this? + QString user; ///< Owner of the process +}; + +using KProcessList = QList; + +/** + * @brief Retrieves the list of currently active processes + * @return Returns the list of currently active processes + * @since 5.57 + */ +KProcessList GetProcessList(); + +#endif // KPROCESSLIST_H diff --git a/src/lib/util/kprocesslist_unix.cpp b/src/lib/util/kprocesslist_unix.cpp new file mode 100644 --- /dev/null +++ b/src/lib/util/kprocesslist_unix.cpp @@ -0,0 +1,137 @@ +/************************************************************************** +** +** This file is part of the KDE Frameworks +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2019 David Hallas +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "kprocesslist.h" + +#include +#include + +namespace { + +bool isUnixProcessId(const QString &procname) +{ + for (int i = 0; i != procname.size(); ++i) { + if (!procname.at(i).isDigit()) + return false; + } + return true; +} + +// Determine UNIX processes by running ps +KProcessList unixProcessListPS() +{ +#ifdef Q_OS_MAC + // command goes last, otherwise it is cut off + static const char formatC[] = "pid state user command"; +#else + static const char formatC[] = "pid,state,user,cmd"; +#endif + KProcessList rc; + QProcess psProcess; + QStringList args; + args << QStringLiteral("-e") << QStringLiteral("-o") << QLatin1String(formatC); + psProcess.start(QStringLiteral("ps"), args); + if (!psProcess.waitForStarted()) + return rc; + psProcess.waitForFinished(); + QByteArray output = psProcess.readAllStandardOutput(); + // Split "457 S+ /Users/foo.app" + const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n')); + const int lineCount = lines.size(); + const QChar blank = QLatin1Char(' '); + for (int l = 1; l < lineCount; l++) { // Skip header + const QString line = lines.at(l).simplified(); + // we can't just split on blank as the process name might + // contain them + const int endOfPid = line.indexOf(blank); + const int endOfState = line.indexOf(blank, endOfPid+1); + const int endOfUser = line.indexOf(blank, endOfState+1); + if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) { + KProcessInfo processInfo; + processInfo.pid = line.left(endOfPid).toUInt(); + processInfo.state = line.mid(endOfPid+1, endOfState-endOfPid-1); + processInfo.user = line.mid(endOfState+1, endOfUser-endOfState-1); + processInfo.name = line.right(line.size()-endOfUser-1); + rc.push_back(processInfo); + } + } + + return rc; +} + +} // unnamed namespace + +// Determine UNIX processes by reading "/proc". Default to ps if +// it does not exist +KProcessList GetProcessList() +{ + const QDir procDir(QStringLiteral("/proc/")); + if (!procDir.exists()) + return unixProcessListPS(); + KProcessList rc; + const QStringList procIds = procDir.entryList(); + if (procIds.isEmpty()) + return rc; + for (const QString &procId : procIds) { + if (!isUnixProcessId(procId)) + continue; + QString filename = QStringLiteral("/proc/"); + filename += procId; + filename += QLatin1String("/stat"); + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) + continue; // process may have exited + + const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' ')); + KProcessInfo processInfo; + processInfo.pid = procId.toUInt(); + processInfo.name = data.at(1); + if (processInfo.name.startsWith(QLatin1Char('(')) && processInfo.name.endsWith(QLatin1Char(')'))) { + processInfo.name.truncate(processInfo.name.size() - 1); + processInfo.name.remove(0, 1); + } + processInfo.state = data.at(2); + // PPID is element 3 + + processInfo.user = QFileInfo(file).owner(); + file.close(); + + QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline")); + if (cmdFile.open(QFile::ReadOnly)) { + QByteArray cmd = cmdFile.readAll(); + cmd.replace('\0', ' '); + if (!cmd.isEmpty()) + processInfo.name = QString::fromLocal8Bit(cmd).trimmed(); + } + cmdFile.close(); + rc.push_back(processInfo); + } + return rc; +} diff --git a/src/lib/util/kprocesslist_win.cpp b/src/lib/util/kprocesslist_win.cpp new file mode 100644 --- /dev/null +++ b/src/lib/util/kprocesslist_win.cpp @@ -0,0 +1,143 @@ +/************************************************************************** +** +** This file is part of the KDE Frameworks +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2019 David Hallas +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "kprocesslist.h" + +#include + +// Enable Win API of XP SP1 and later +#ifdef Q_OS_WIN +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0502 +# endif +# include +# if !defined(PROCESS_SUSPEND_RESUME) // Check flag for MinGW +# define PROCESS_SUSPEND_RESUME (0x0800) +# endif // PROCESS_SUSPEND_RESUME +#endif // Q_OS_WIN + +#include +#include + +// Resolve QueryFullProcessImageNameW out of kernel32.dll due +// to incomplete MinGW import libs and it not being present +// on Windows XP. +static inline BOOL queryFullProcessImageName(HANDLE h, DWORD flags, LPWSTR buffer, DWORD *size) +{ + // Resolve required symbols from the kernel32.dll + typedef BOOL (WINAPI *QueryFullProcessImageNameWProtoType)(HANDLE, DWORD, LPWSTR, PDWORD); + static QueryFullProcessImageNameWProtoType queryFullProcessImageNameW = 0; + if (!queryFullProcessImageNameW) { + QLibrary kernel32Lib(QLatin1String("kernel32.dll"), 0); + if (kernel32Lib.isLoaded() || kernel32Lib.load()) { + queryFullProcessImageNameW + = (QueryFullProcessImageNameWProtoType)kernel32Lib.resolve( + "QueryFullProcessImageNameW"); + } + } + if (!queryFullProcessImageNameW) + return FALSE; + // Read out process + return (*queryFullProcessImageNameW)(h, flags, buffer, size); +} + +struct ProcessInfo { + QString imageName; + QString processOwner; +}; + +static inline ProcessInfo processInfo(DWORD processId) +{ + ProcessInfo pi; + HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, TOKEN_READ, processId); + if (handle == INVALID_HANDLE_VALUE) + return pi; + WCHAR buffer[MAX_PATH]; + DWORD bufSize = MAX_PATH; + if (queryFullProcessImageName(handle, 0, buffer, &bufSize)) + pi.imageName = QString::fromUtf16(reinterpret_cast(buffer)); + + HANDLE processTokenHandle = NULL; + if (!OpenProcessToken(handle, TOKEN_READ, &processTokenHandle) || !processTokenHandle) + return pi; + + DWORD size = 0; + GetTokenInformation(processTokenHandle, TokenUser, NULL, 0, &size); + + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + QByteArray buf; + buf.resize(size); + PTOKEN_USER userToken = reinterpret_cast(buf.data()); + if (userToken + && GetTokenInformation(processTokenHandle, TokenUser, userToken, size, &size)) { + SID_NAME_USE sidNameUse; + TCHAR user[MAX_PATH] = { 0 }; + DWORD userNameLength = MAX_PATH; + TCHAR domain[MAX_PATH] = { 0 }; + DWORD domainNameLength = MAX_PATH; + + if (LookupAccountSid(NULL, + userToken->User.Sid, + user, + &userNameLength, + domain, + &domainNameLength, + &sidNameUse)) + pi.processOwner = QString::fromUtf16(reinterpret_cast(user)); + } + } + + CloseHandle(processTokenHandle); + CloseHandle(handle); + return pi; +} + +KProcessList GetProcessList() +{ + KProcessList rc; + + PROCESSENTRY32 pe; + pe.dwSize = sizeof(PROCESSENTRY32); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) + return rc; + + for (bool hasNext = Process32First(snapshot, &pe); hasNext; hasNext = Process32Next(snapshot, &pe)) { + KProcessInfo processInfo; + processInfo.pid = pe.th32ProcessID; + processInfo.name = QString::fromUtf16(reinterpret_cast(pe.szExeFile)); + const ProcessInfo processInf = processInfo(pe.th32ProcessID); + processInfo.image = processInf.imageName; + processInfo.user = processInf.processOwner; + rc.push_back(processInfo); + } + CloseHandle(snapshot); + return rc; +}