File indexing completed on 2024-04-28 16:49:54

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