File indexing completed on 2024-04-28 05:31:37

0001 /*
0002     SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "process_controller.h"
0008 
0009 #include <functional>
0010 
0011 #include <QWindow>
0012 
0013 #include <KAuth/Action>
0014 #include <KAuth/ExecuteJob>
0015 
0016 #include <KLocalizedString>
0017 
0018 #include "processcore_debug.h"
0019 #include "processes_local_p.h"
0020 
0021 using namespace KSysGuard;
0022 
0023 struct ApplyResult {
0024     ProcessController::Result resultCode = ProcessController::Result::Success;
0025     QList<int> unchanged;
0026 };
0027 
0028 class ProcessController::Private
0029 {
0030 public:
0031     ApplyResult applyToPids(const QList<int> &pids, const std::function<Processes::Error(int)> &function);
0032     ProcessController::Result runKAuthAction(const QString &actionId, const QList<int> &pids, const QVariantMap &options);
0033     QList<int> listToVector(const QList<long long> &list);
0034     QList<int> listToVector(const QVariantList &list);
0035 
0036     QWindow *window = nullptr;
0037 };
0038 
0039 // Note: This instance is only to have access to the platform-specific code
0040 // for sending signals, setting priority etc. Therefore, it should never be
0041 // used to access information about processes.
0042 Q_GLOBAL_STATIC(ProcessesLocal, s_localProcesses);
0043 
0044 ProcessController::ProcessController(QObject *parent)
0045     : QObject(parent)
0046     , d(new Private)
0047 {
0048 }
0049 
0050 KSysGuard::ProcessController::~ProcessController()
0051 {
0052     // Empty destructor needed for std::unique_ptr to incomplete class.
0053 }
0054 
0055 QWindow *KSysGuard::ProcessController::window() const
0056 {
0057     return d->window;
0058 }
0059 
0060 void KSysGuard::ProcessController::setWindow(QWindow *window)
0061 {
0062     d->window = window;
0063 }
0064 
0065 ProcessController::Result ProcessController::sendSignal(const QList<int> &pids, int signal)
0066 {
0067     qCDebug(LIBKSYSGUARD_PROCESSCORE) << "Sending signal" << signal << "to" << pids;
0068 
0069     auto result = d->applyToPids(pids, [signal](int pid) {
0070         return s_localProcesses->sendSignal(pid, signal);
0071     });
0072     if (result.unchanged.isEmpty()) {
0073         return result.resultCode;
0074     }
0075 
0076     return d->runKAuthAction(QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal"), result.unchanged, {{QStringLiteral("signal"), signal}});
0077 }
0078 
0079 KSysGuard::ProcessController::Result KSysGuard::ProcessController::sendSignal(const QList<long long> &pids, int signal)
0080 {
0081     return sendSignal(d->listToVector(pids), signal);
0082 }
0083 
0084 KSysGuard::ProcessController::Result KSysGuard::ProcessController::sendSignal(const QVariantList &pids, int signal)
0085 {
0086     return sendSignal(d->listToVector(pids), signal);
0087 }
0088 
0089 ProcessController::Result ProcessController::setPriority(const QList<int> &pids, int priority)
0090 {
0091     auto result = d->applyToPids(pids, [priority](int pid) {
0092         return s_localProcesses->setNiceness(pid, priority);
0093     });
0094     if (result.unchanged.isEmpty()) {
0095         return result.resultCode;
0096     }
0097 
0098     return d->runKAuthAction(QStringLiteral("org.kde.ksysguard.processlisthelper.renice"), result.unchanged, {{QStringLiteral("nicevalue"), priority}});
0099 }
0100 
0101 KSysGuard::ProcessController::Result KSysGuard::ProcessController::setPriority(const QList<long long> &pids, int priority)
0102 {
0103     return setPriority(d->listToVector(pids), priority);
0104 }
0105 
0106 KSysGuard::ProcessController::Result KSysGuard::ProcessController::setPriority(const QVariantList &pids, int priority)
0107 {
0108     return setPriority(d->listToVector(pids), priority);
0109 }
0110 
0111 ProcessController::Result ProcessController::setCPUScheduler(const QList<int> &pids, Process::Scheduler scheduler, int priority)
0112 {
0113     if (scheduler == KSysGuard::Process::Other || scheduler == KSysGuard::Process::Batch) {
0114         priority = 0;
0115     }
0116 
0117     auto result = d->applyToPids(pids, [scheduler, priority](int pid) {
0118         return s_localProcesses->setScheduler(pid, scheduler, priority);
0119     });
0120     if (result.unchanged.isEmpty()) {
0121         return result.resultCode;
0122     }
0123 
0124     return d->runKAuthAction(QStringLiteral("org.kde.ksysguard.processlisthelper.changecpuscheduler"),
0125                              result.unchanged, //
0126                              {{QStringLiteral("cpuScheduler"), scheduler}, {QStringLiteral("cpuSchedulerPriority"), priority}});
0127 }
0128 
0129 KSysGuard::ProcessController::Result KSysGuard::ProcessController::setCPUScheduler(const QList<long long> &pids, Process::Scheduler scheduler, int priority)
0130 {
0131     return setCPUScheduler(d->listToVector(pids), scheduler, priority);
0132 }
0133 
0134 KSysGuard::ProcessController::Result KSysGuard::ProcessController::setCPUScheduler(const QVariantList &pids, Process::Scheduler scheduler, int priority)
0135 {
0136     return setCPUScheduler(d->listToVector(pids), scheduler, priority);
0137 }
0138 
0139 ProcessController::Result ProcessController::setIOScheduler(const QList<int> &pids, Process::IoPriorityClass priorityClass, int priority)
0140 {
0141     if (!s_localProcesses->supportsIoNiceness()) {
0142         return Result::Unsupported;
0143     }
0144 
0145     if (priorityClass == KSysGuard::Process::None) {
0146         priorityClass = KSysGuard::Process::BestEffort;
0147     }
0148 
0149     if (priorityClass == KSysGuard::Process::Idle) {
0150         priority = 0;
0151     }
0152 
0153     auto result = d->applyToPids(pids, [priorityClass, priority](int pid) {
0154         return s_localProcesses->setIoNiceness(pid, priorityClass, priority);
0155     });
0156     if (result.unchanged.isEmpty()) {
0157         return result.resultCode;
0158     }
0159 
0160     return d->runKAuthAction(QStringLiteral("org.kde.ksysguard.processlisthelper.changeioscheduler"),
0161                              result.unchanged, //
0162                              {{QStringLiteral("ioScheduler"), priorityClass}, {QStringLiteral("ioSchedulerPriority"), priority}});
0163 }
0164 
0165 KSysGuard::ProcessController::Result
0166 KSysGuard::ProcessController::setIOScheduler(const QList<long long> &pids, Process::IoPriorityClass priorityClass, int priority)
0167 {
0168     return setIOScheduler(d->listToVector(pids), priorityClass, priority);
0169 }
0170 
0171 KSysGuard::ProcessController::Result
0172 KSysGuard::ProcessController::setIOScheduler(const QVariantList &pids, Process::IoPriorityClass priorityClass, int priority)
0173 {
0174     return setIOScheduler(d->listToVector(pids), priorityClass, priority);
0175 }
0176 
0177 QString ProcessController::resultToString(Result result)
0178 {
0179     switch (result) {
0180     case Result::Success:
0181         return i18n("Success");
0182     case Result::InsufficientPermissions:
0183         return i18n("Insufficient permissions.");
0184     case Result::NoSuchProcess:
0185         return i18n("No matching process was found.");
0186     case Result::Unsupported:
0187         return i18n("Not supported on the current system.");
0188     case Result::UserCancelled:
0189         return i18n("The user cancelled.");
0190     case Result::Error:
0191         return i18n("An unspecified error occurred.");
0192     default:
0193         return i18n("An unknown error occurred.");
0194     }
0195 }
0196 
0197 ApplyResult KSysGuard::ProcessController::Private::applyToPids(const QList<int> &pids, const std::function<Processes::Error(int)> &function)
0198 {
0199     ApplyResult result;
0200 
0201     for (auto pid : pids) {
0202         auto error = function(pid);
0203         switch (error) {
0204         case KSysGuard::Processes::InsufficientPermissions:
0205         case KSysGuard::Processes::Unknown:
0206             result.unchanged << pid;
0207             result.resultCode = Result::InsufficientPermissions;
0208             break;
0209         case Processes::InvalidPid:
0210         case Processes::ProcessDoesNotExistOrZombie:
0211         case Processes::InvalidParameter:
0212             result.resultCode = Result::NoSuchProcess;
0213             break;
0214         case Processes::NotSupported:
0215             result.resultCode = Result::Unsupported;
0216             break;
0217         case Processes::NoError:
0218             break;
0219         }
0220     }
0221     return result;
0222 }
0223 
0224 ProcessController::Result ProcessController::Private::runKAuthAction(const QString &actionId, const QList<int> &pids, const QVariantMap &options)
0225 {
0226     KAuth::Action action(actionId);
0227     if (!action.isValid()) {
0228         qCWarning(LIBKSYSGUARD_PROCESSCORE) << "Executing KAuth action" << actionId << "failed because it is an invalid action";
0229         return Result::InsufficientPermissions;
0230     }
0231     action.setParentWindow(window);
0232     action.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
0233 
0234     const int processCount = pids.count();
0235     for (int i = 0; i < processCount; ++i) {
0236         action.addArgument(QStringLiteral("pid%1").arg(i), pids.at(i));
0237     }
0238     action.addArgument(QStringLiteral("pidcount"), processCount);
0239 
0240     for (auto itr = options.cbegin(); itr != options.cend(); ++itr) {
0241         action.addArgument(itr.key(), itr.value());
0242     }
0243 
0244     KAuth::ExecuteJob *job = action.execute();
0245     if (job->exec()) {
0246         return Result::Success;
0247     } else {
0248         if (job->error() == KAuth::ActionReply::UserCancelledError) {
0249             return Result::UserCancelled;
0250         }
0251 
0252         if (job->error() == KAuth::ActionReply::AuthorizationDeniedError) {
0253             return Result::InsufficientPermissions;
0254         }
0255 
0256         qCWarning(LIBKSYSGUARD_PROCESSCORE) << "Executing KAuth action" << actionId << "failed with error code" << job->error();
0257         qCWarning(LIBKSYSGUARD_PROCESSCORE) << job->errorString();
0258         return Result::Error;
0259     }
0260 }
0261 
0262 QList<int> KSysGuard::ProcessController::Private::listToVector(const QList<long long> &list)
0263 {
0264     QList<int> vector;
0265     std::transform(list.cbegin(), list.cend(), std::back_inserter(vector), [](long long entry) {
0266         return entry;
0267     });
0268     return vector;
0269 }
0270 
0271 QList<int> KSysGuard::ProcessController::Private::listToVector(const QVariantList &list)
0272 {
0273     QList<int> vector;
0274     std::transform(list.cbegin(), list.cend(), std::back_inserter(vector), [](const QVariant &entry) {
0275         return entry.toInt();
0276     });
0277     return vector;
0278 }