Commit cb3ccfc6 authored by Arjen Hiemstra's avatar Arjen Hiemstra
Browse files

Move code to send signals and set priority of processes to a dedicated object

Summary:
This pulls the code out of KSysGuardProcessList and introduces it in a
new class called ProcessController. This should help make that code more
reusable and allow it to eventually be exposed to QML.

Test Plan:
Start ksysguard, try to send signals to processes and set priorities, including
root-owned processes. Everything should still work.

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: davidedmundson, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D23566
parent 3a285053
......@@ -86,6 +86,9 @@ add_definitions(-DQT_NO_CAST_TO_ASCII)
add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT)
#add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
add_subdirectory( lsofui )
add_subdirectory( processcore )
add_subdirectory( processui )
......
......@@ -9,6 +9,7 @@ set(ksysguard_LIB_SRCS
processes_remote_p.cpp
processes_base_p.cpp
processes_atop_p.cpp
process_controller.cpp
)
ecm_qt_declare_logging_category(ksysguard_LIB_SRCS HEADER processcore_debug.h IDENTIFIER LIBKSYSGUARD_PROCESSCORE CATEGORY_NAME org.kde.libksysguard.processcore)
......@@ -21,6 +22,7 @@ target_link_libraries(processcore
Qt5::Core
PRIVATE
KF5::I18n
KF5::AuthCore
${ZLIB_LIBRARIES}
)
......@@ -42,6 +44,7 @@ install(TARGETS processcore EXPORT libksysguardLibraryTargets ${KDE_INSTALL_TARG
install( FILES
processes.h
process.h
process_controller.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/ksysguard/processcore
COMPONENT Devel
)
......
/*
* This file is part of KSysGuard.
* Copyright 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "process_controller.h"
#include <functional>
#include <KAuth/KAuthAction>
#include <KAuth/KAuthExecuteJob>
#include "processes_local_p.h"
#include "processcore_debug.h"
using namespace KSysGuard;
struct ApplyResult
{
ProcessController::Result resultCode = ProcessController::Result::Success;
QVector<int> unchanged;
};
class ProcessController::Private
{
public:
ApplyResult applyToPids(const QVector<int> &pids, const std::function<bool(int)> &function);
ProcessController::Result runKAuthAction(const QString &actionId, const QVector<int> &pids, const QVariantMap &options);
QVector<int> listToVector(const QList<long long> &list);
QWidget *widget;
// Note: This instance is only to have access to the platform-specific code
// for sending signals, setting priority etc. Therefore, it should never be
// used to access information about processes.
static std::unique_ptr<ProcessesLocal> localProcesses;
};
std::unique_ptr<ProcessesLocal> ProcessController::Private::localProcesses;
ProcessController::ProcessController(QObject* parent)
: QObject(parent), d(new Private)
{
if (!d->localProcesses) {
d->localProcesses = std::make_unique<ProcessesLocal>();
}
}
KSysGuard::ProcessController::~ProcessController()
{
// Empty destructor needed for std::unique_ptr to incomplete class.
}
QWidget * KSysGuard::ProcessController::widget() const
{
return d->widget;
}
void KSysGuard::ProcessController::setWidget(QWidget* widget)
{
d->widget = widget;
}
ProcessController::Result ProcessController::sendSignal(const QVector<int>& pids, int signal)
{
qCDebug(LIBKSYSGUARD_PROCESSCORE) << "Sending signal" << signal << "to" << pids;
auto result = d->applyToPids(pids, [this, signal](int pid) { return d->localProcesses->sendSignal(pid, signal); });
if (result.unchanged.isEmpty()) {
return result.resultCode;
}
return d->runKAuthAction(
QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal"),
result.unchanged,
{ {QStringLiteral("signal"), signal} }
);
}
KSysGuard::ProcessController::Result KSysGuard::ProcessController::sendSignal(const QList<long long>& pids, int signal)
{
return sendSignal(d->listToVector(pids), signal);
}
ProcessController::Result ProcessController::setPriority(const QVector<int>& pids, int priority)
{
auto result = d->applyToPids(pids, [this, priority](int pid) { return d->localProcesses->setNiceness(pid, priority); });
if (result.unchanged.isEmpty()) {
return result.resultCode;
}
return d->runKAuthAction(
QStringLiteral("org.kde.ksysguard.processlisthelper.renice"),
result.unchanged,
{ { QStringLiteral("nicevalue"), priority } }
);
}
KSysGuard::ProcessController::Result KSysGuard::ProcessController::setPriority(const QList<long long>& pids, int priority)
{
return setPriority(d->listToVector(pids), priority);
}
ProcessController::Result ProcessController::setCPUScheduler(const QVector<int>& pids, Process::Scheduler scheduler, int priority)
{
if (scheduler == KSysGuard::Process::Other || scheduler == KSysGuard::Process::Batch) {
priority = 0;
}
auto result = d->applyToPids(pids, [this, scheduler, priority](int pid) {
return d->localProcesses->setScheduler(pid, scheduler, priority);
});
if (result.unchanged.isEmpty()) {
return result.resultCode;
}
return d->runKAuthAction(
QStringLiteral("org.kde.ksysguard.processlisthelper.changecpuscheduler"),
result.unchanged,
{{QStringLiteral("cpuScheduler"), scheduler}, {QStringLiteral("cpuSchedulerPriority"), priority}}
);
}
KSysGuard::ProcessController::Result KSysGuard::ProcessController::setCPUScheduler(const QList<long long>& pids, Process::Scheduler scheduler, int priority)
{
return setCPUScheduler(d->listToVector(pids), scheduler, priority);
}
ProcessController::Result ProcessController::setIOScheduler(const QVector<int>& pids, Process::IoPriorityClass priorityClass, int priority)
{
if (!d->localProcesses->supportsIoNiceness()) {
return Result::Unsupported;
}
if (priorityClass == KSysGuard::Process::None) {
priorityClass = KSysGuard::Process::BestEffort;
}
if (priorityClass == KSysGuard::Process::Idle) {
priority = 0;
}
auto result = d->applyToPids(pids, [this, priorityClass, priority](int pid) {
return d->localProcesses->setIoNiceness(pid, priorityClass, priority);
});
if (result.unchanged.isEmpty()) {
return result.resultCode;
}
return d->runKAuthAction(
QStringLiteral("org.kde.ksysguard.processlisthelper.changeioscheduler"),
result.unchanged,
{{QStringLiteral("ioScheduler"), priorityClass}, {QStringLiteral("ioSchedulerPriority"), priority}}
);
}
KSysGuard::ProcessController::Result KSysGuard::ProcessController::setIOScheduler(const QList<long long>& pids, Process::IoPriorityClass priorityClass, int priority)
{
return setIOScheduler(d->listToVector(pids), priorityClass, priority);
}
ApplyResult KSysGuard::ProcessController::Private::applyToPids(const QVector<int>& pids, const std::function<bool(int)>& function)
{
ApplyResult result;
for (auto pid : pids) {
auto success = function(pid);
if (!success
&& (localProcesses->errorCode == KSysGuard::Processes::InsufficientPermissions
|| localProcesses->errorCode == KSysGuard::Processes::Unknown)) {
result.unchanged << pid;
result.resultCode = Result::InsufficientPermissions;
} else if (result.resultCode == Result::Success) {
switch (localProcesses->errorCode) {
case Processes::InvalidPid:
case Processes::ProcessDoesNotExistOrZombie:
case Processes::InvalidParameter:
result.resultCode = Result::NoSuchProcess;
break;
case Processes::NotSupported:
result.resultCode = Result::Unsupported;
break;
default:
result.resultCode = Result::Unknown;
break;
}
}
}
return result;
}
ProcessController::Result ProcessController::Private::runKAuthAction(const QString& actionId, const QVector<int> &pids, const QVariantMap& options)
{
KAuth::Action action(actionId);
if (!action.isValid()) {
qCWarning(LIBKSYSGUARD_PROCESSCORE) << "Executing KAuth action" << actionId << "failed because it is an invalid action";
return Result::InsufficientPermissions;
}
action.setParentWidget(widget);
action.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
const int processCount = pids.count();
for (int i = 0; i < processCount; ++i) {
action.addArgument(QStringLiteral("pid%1").arg(i), pids.at(i));
}
action.addArgument(QStringLiteral("pidcount"), processCount);
for (auto itr = options.cbegin(); itr != options.cend(); ++itr) {
action.addArgument(itr.key(), itr.value());
}
KAuth::ExecuteJob *job = action.execute();
if(job->exec()) {
return Result::Success;
} else {
if (job->error() == KAuth::ActionReply::UserCancelledError) {
return Result::UserCancelled;
}
if (job->error() == KAuth::ActionReply::AuthorizationDeniedError) {
return Result::InsufficientPermissions;
}
qCWarning(LIBKSYSGUARD_PROCESSCORE) << "Executing KAuth action" << actionId << "failed with error code" << job->error();
qCWarning(LIBKSYSGUARD_PROCESSCORE) << job->errorString();
return Result::Error;
}
}
QVector<int> KSysGuard::ProcessController::Private::listToVector(const QList<long long>& list)
{
QVector<int> vector;
std::transform(list.cbegin(), list.cend(), std::back_inserter(vector), [](long long entry) { return entry; });
return vector;
}
/*
* This file is part of KSysGuard.
* Copyright 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KSYSGUARD_PROCESSCONTROLLER_H
#define KSYSGUARD_PROCESSCONTROLLER_H
#include <memory>
#include <QObject>
#include "process.h"
class QWidget;
/**
* Control processes' priority, scheduling and sending signals.
*
* This class contains methods for sending signals to processes, setting their
* priority and setting their scheduler. It will first try to manipulate the
* processes directly, if that fails, it will use KAuth to try and perform the
* action as root.
*/
namespace KSysGuard {
class Q_DECL_EXPORT ProcessController : public QObject
{
Q_OBJECT
public:
ProcessController(QObject* parent = nullptr);
~ProcessController();
/**
* A signal that can be sent to a process.
*/
enum Signal {
StopSignal,
ContinueSignal,
HangupSignal,
InterruptSignal,
TerminateSignal,
KillSignal,
User1Signal,
User2Signal
};
Q_ENUM(Signal)
/**
* What kind of result a call to one of the ProcessController methods had.
*/
enum class Result {
Unknown, ///< Something happened, we just do not know what.
Success, ///< Everything went alright.
InsufficientPermissions, ///< Some processes require privileges to modify and we failed getting those.
NoSuchProcess, ///< Tried to modify a process that no longer exists.
Unsupported, ///< The specified action is not supported.
UserCancelled, ///< The user cancelled the action, usually when requesting privileges.
Error, ///< An error occurred when requesting privileges.
};
Q_ENUM(Result)
/**
* The widget used as parent for any dialogs that get shown.
*/
QWidget *widget() const;
/**
* Set the widget to use as parent for dialogs.
*
* \param widget The widget to use.
*/
void setWidget(QWidget *widget);
/**
* Send a signal to a number of processes.
*
* This will send \p signal to all processes in \p pids. Should a number of
* these be owned by different users, an attempt will be made to send the
* signal as root using KAuth.
*
* \param pids A vector of pids to send the signal to.
* \param signal The signal to send. See Signal for possible values.
*
* \return A Result value that indicates whether the action succeeded. Note
* that a non-Success result may indicate any of the processes in
* \p pids encountered that result.
*/
Q_INVOKABLE Result sendSignal(const QVector<int> &pids, int signal);
/**
* \overload Result sendSignal(const QVector<int> &pids, int signal)
*/
Q_INVOKABLE Result sendSignal(const QList<long long> &pids, int signal);
/**
* Set the priority (niceness) of a number of processes.
*
* This will set the priority of all processes in \p pids to \p priority.
* Should a number of these be owned by different users, an attempt will be
* made to send the signal as root using KAuth.
*
* \param pids A vector of pids to set the priority of.
* \param priority The priority to set. Lower means higher priority.
*
* \return A Result value that indicates whether the action succeeded. Note
* that a non-Success result may indicate any of the processes in
* \p pids encountered that result.
*/
Q_INVOKABLE Result setPriority(const QVector<int> &pids, int priority);
/**
* \overload Result setPriority(const QVector<int> &pids, int priority)
*/
Q_INVOKABLE Result setPriority(const QList<long long> &pids, int priority);
/**
* Set the CPU scheduling policy and priority of a number of processes.
*
* This will set the CPU scheduling policy and priority of all processes in
* \p pids to \p scheduler and \p priority. Should a number of these be
* owned by different users, an attempt will be made to send the signal as
* root using KAuth.
*
* \param pids A vector of pids to set the scheduler of.
* \param scheduler The scheduling policy to use.
* \param priority The priority to set. Lower means higher priority.
*
* \return A Result value that indicates whether the action succeeded. Note
* that a non-Success result may indicate any of the processes in
* \p pids encountered that result.
*/
Q_INVOKABLE Result setCPUScheduler(const QVector<int> &pids, Process::Scheduler scheduler, int priority);
/**
* \overload Result setCPUScheduler(const QVector<int> &pids, Process::Scheduler scheduler, int priority)
*/
Q_INVOKABLE Result setCPUScheduler(const QList<long long> &pids, Process::Scheduler scheduler, int priority);
/**
* Set the IO scheduling policy and priority of a number of processes.
*
* This will set the IO scheduling policy and priority of all processes in
* \p pids to \p priorityClass and \p priority. Should a number of these be
* owned by different users, an attempt will be made to send the signal as
* root using KAuth.
*
* \param pids A vector of pids to set the scheduler of.
* \param priorityClass The scheduling policy to use.
* \param priority The priority to set. Lower means higher priority.
*
* \return A Result value that indicates whether the action succeeded. Note
* that a non-Success result may indicate any of the processes in
* \p pids encountered that result.
*/
Q_INVOKABLE Result setIOScheduler(const QVector<int> &pids, Process::IoPriorityClass priorityClass, int priority);
/**
* \overload Result setIOScheduler(const QVector<int> &pids, Process::IoPriorityClass priorityClass, int priority)
*/
Q_INVOKABLE Result setIOScheduler(const QList<long long> &pids, Process::IoPriorityClass priorityClass, int priority);
private:
class Private;
const std::unique_ptr<Private> d;
};
}
#endif // KSYSGUARD_PROCESSCONTROLLER_H
......@@ -63,6 +63,7 @@
#include "ReniceDlg.h"
#include "ui_ProcessWidgetUI.h"
#include "scripting.h"
#include "process_controller.h"
#include <sys/types.h>
#include <unistd.h>
......@@ -248,6 +249,8 @@ struct KSysGuardProcessListPrivate {
*/
ProcessFilter mFilterModel;
KSysGuard::ProcessController *mProcessController = nullptr;
/** The graphical user interface for this process list widget, auto-generated by Qt Designer */
Ui::ProcessWidget *mUi;
......@@ -298,6 +301,9 @@ KSysGuardProcessList::KSysGuardProcessList(QWidget* parent, const QString &hostN
qRegisterMetaType<QList<long long> >();
qDBusRegisterMetaType<QList<long long> >();
d->mProcessController = new KSysGuard::ProcessController(this);
d->mProcessController->setWidget(window());
d->mUpdateIntervalMSecs = 0; //Set process to not update manually by default
d->mUi->setupUi(this);
d->mFilterModel.setSourceModel(&d->mModel);
......@@ -1129,29 +1135,13 @@ void KSysGuardProcessList::setUpdateIntervalMSecs(int intervalMSecs)
bool KSysGuardProcessList::reniceProcesses(const QList<long long> &pids, int niceValue)
{
QList< long long> unreniced_pids;
for (int i = 0; i < pids.size(); ++i) {
bool success = d->mModel.processController()->setNiceness(pids.at(i), niceValue);
if(!success) {
unreniced_pids << pids.at(i);
}
}
if(unreniced_pids.isEmpty()) return true; //All processes were reniced successfully
if(!d->mModel.isLocalhost()) return false; //We can't use kauth to renice non-localhost processes
KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.renice"));
action.setParentWidget(window());
d->setupKAuthAction( action, unreniced_pids);
action.addArgument(QStringLiteral("nicevalue"), niceValue);
KAuth::ExecuteJob *job = action.execute();
if (job->exec()) {
auto result = d->mProcessController->setPriority(pids, niceValue);
if (result == KSysGuard::ProcessController::Result::Success) {
updateList();
} else if (!job->exec()) {
return true;
} else if (result == KSysGuard::ProcessController::Result::Error) {
KMessageBox::sorry(this, i18n("You do not have the permission to renice the process and there "
"was a problem trying to run as root. Error %1 %2", job->error(), job->errorString()));
return false;
"was a problem trying to run as root."));
}
return true;
}
......@@ -1289,95 +1279,44 @@ void KSysGuardProcessList::reniceSelectedProcesses()
bool KSysGuardProcessList::changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority)
{
if(newIoSched == KSysGuard::Process::None) newIoSched = KSysGuard::Process::BestEffort;
if(newIoSched == KSysGuard::Process::Idle) newIoSchedPriority = 0;
QList< long long> unchanged_pids;
for (int i = 0; i < pids.size(); ++i) {
bool success = d->mModel.processController()->setIoNiceness(pids.at(i), newIoSched, newIoSchedPriority);
if(!success) {
unchanged_pids << pids.at(i);
}
}
if(unchanged_pids.isEmpty()) return true;
if(!d->mModel.isLocalhost()) return false; //We can't use kauth to affect non-localhost processes
KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.changeioscheduler"));
action.setParentWidget(window());
d->setupKAuthAction( action, unchanged_pids);
action.addArgument(QStringLiteral("ioScheduler"), (int)newIoSched);
action.addArgument(QStringLiteral("ioSchedulerPriority"), newIoSchedPriority);
KAuth::ExecuteJob *job = action.execute();
if (job->exec()) {
auto result = d->mProcessController->setIOScheduler(pids, newIoSched, newIoSchedPriority);
if (result == KSysGuard::ProcessController::Result::Success) {
updateList();
} else if (!job->exec()) {
return true;
} else if (result == KSysGuard::ProcessController::Result::Error) {
KMessageBox::sorry(this, i18n("You do not have the permission to change the I/O priority of the process and there "
"was a problem trying to run as root. Error %1 %2", job->error(), job->errorString()));
return false;
"was a problem trying to run as root."));
}
return true;
return false;
}
bool KSysGuardProcessList::changeCpuScheduler(const QList< long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority)
{
if(newCpuSched == KSysGuard::Process::Other || newCpuSched == KSysGuard::Process::Batch) newCpuSchedPriority = 0;
QList< long long> unchanged_pids;
for (int i = 0; i < pids.size(); ++i) {
bool success = d->mModel.processController()->setScheduler(pids.at(i), newCpuSched, newCpuSchedPriority);
if(!success) {
unchanged_pids << pids.at(i);
}
}
if(unchanged_pids.isEmpty()) return true;
if(!d->mModel.isLocalhost()) return false; //We can't use KAuth to affect non-localhost processes
KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.changecpuscheduler"));
action.setParentWidget(window());
d->setupKAuthAction( action, unchanged_pids);
action.addArgument(QStringLiteral("cpuScheduler"), (int)newCpuSched);
action.addArgument(QStringLiteral("cpuSchedulerPriority"), newCpuSchedPriority);
KAuth::ExecuteJob *job = action.execute();
auto result = d->mProcessController->setCPUScheduler(pids, newCpuSched, newCpuSchedPriority);
if (job->exec()) {
if (result == KSysGuard::ProcessController::Result::Success) {
updateList();
} else if (!job->exec()) {
return true;
} else if (result == KSysGuard::ProcessController::Result::Error) {
KMessageBox::sorry(this, i18n("You do not have the permission to change the CPU Scheduler for the process and there "
"was a problem trying to run as root. Error %1 %2", job->error(), job->errorString()));
return false;
"was a problem trying to run as root."));
}
return true;
return false;
}