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

0001 /*
0002     SPDX-FileCopyrightText: 2007 John Tapsell <tapsell@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "process.h"
0008 #include "processes_local_p.h"
0009 #include "read_procsmaps_runnable.h"
0010 
0011 #include <klocalizedstring.h>
0012 
0013 #include <QByteArray>
0014 #include <QDir>
0015 #include <QFile>
0016 #include <QHash>
0017 #include <QSet>
0018 #include <QTextStream>
0019 #include <QThreadPool>
0020 
0021 // for sysconf
0022 #include <unistd.h>
0023 // for kill and setNice
0024 #include <dirent.h>
0025 #include <errno.h>
0026 #include <signal.h>
0027 #include <stdlib.h>
0028 #include <sys/resource.h>
0029 #include <sys/types.h>
0030 // for ionice
0031 #include <asm/unistd.h>
0032 #include <sys/ptrace.h>
0033 // for getsched
0034 #include <sched.h>
0035 
0036 #define PROCESS_BUFFER_SIZE 1000
0037 
0038 /* For ionice */
0039 extern int sys_ioprio_set(int, int, int);
0040 extern int sys_ioprio_get(int, int);
0041 
0042 #define HAVE_IONICE
0043 /* Check if this system has ionice */
0044 #if !defined(SYS_ioprio_get) || !defined(SYS_ioprio_set)
0045 /* All new kernels have SYS_ioprio_get and _set defined, but for the few that do not, here are the definitions */
0046 #if defined(__i386__)
0047 #define __NR_ioprio_set 289
0048 #define __NR_ioprio_get 290
0049 #elif defined(__ppc__) || defined(__powerpc__)
0050 #define __NR_ioprio_set 273
0051 #define __NR_ioprio_get 274
0052 #elif defined(__x86_64__)
0053 #define __NR_ioprio_set 251
0054 #define __NR_ioprio_get 252
0055 #elif defined(__ia64__)
0056 #define __NR_ioprio_set 1274
0057 #define __NR_ioprio_get 1275
0058 #else
0059 #ifdef __GNUC__
0060 #warning "This architecture does not support IONICE.  Disabling ionice feature."
0061 #endif
0062 #undef HAVE_IONICE
0063 #endif
0064 /* Map these to SYS_ioprio_get */
0065 #define SYS_ioprio_get __NR_ioprio_get
0066 #define SYS_ioprio_set __NR_ioprio_set
0067 
0068 #endif /* !SYS_ioprio_get */
0069 
0070 /* Set up ionice functions */
0071 #ifdef HAVE_IONICE
0072 #define IOPRIO_WHO_PROCESS 1
0073 #define IOPRIO_CLASS_SHIFT 13
0074 
0075 /* Expose the kernel calls to userspace via syscall
0076  * See man ioprio_set  and man ioprio_get   for information on these functions */
0077 static int ioprio_set(int which, int who, int ioprio)
0078 {
0079     return syscall(SYS_ioprio_set, which, who, ioprio);
0080 }
0081 
0082 static int ioprio_get(int which, int who)
0083 {
0084     return syscall(SYS_ioprio_get, which, who);
0085 }
0086 #endif
0087 
0088 namespace KSysGuard
0089 {
0090 class ProcessesLocal::Private
0091 {
0092 public:
0093     Private()
0094     {
0095         mProcDir = opendir("/proc");
0096     }
0097     ~Private();
0098     inline bool readProcStatus(const QString &dir, Process *process);
0099     inline bool readProcStat(const QString &dir, Process *process);
0100     inline bool readProcStatm(const QString &dir, Process *process);
0101     inline bool readProcCmdline(const QString &dir, Process *process);
0102     inline bool readProcCGroup(const QString &dir, Process *process);
0103     inline bool readProcAttr(const QString &dir, Process *process);
0104     inline bool getNiceness(long pid, Process *process);
0105     inline bool getIOStatistics(const QString &dir, Process *process);
0106     QFile mFile;
0107     char mBuffer[PROCESS_BUFFER_SIZE + 1]; // used as a buffer to read data into
0108     DIR *mProcDir;
0109 };
0110 
0111 ProcessesLocal::Private::~Private()
0112 {
0113     closedir(mProcDir);
0114 }
0115 
0116 ProcessesLocal::ProcessesLocal()
0117     : d(new Private())
0118 {
0119 }
0120 bool ProcessesLocal::Private::readProcStatus(const QString &dir, Process *process)
0121 {
0122     mFile.setFileName(dir + QStringLiteral("status"));
0123     if (!mFile.open(QIODevice::ReadOnly))
0124         return false; /* process has terminated in the meantime */
0125 
0126     process->setUid(0);
0127     process->setGid(0);
0128     process->setTracerpid(-1);
0129     process->setNumThreads(0);
0130     process->setNoNewPrivileges(0);
0131 
0132     int size;
0133     int found = 0; // count how many fields we found
0134     while ((size = mFile.readLine(mBuffer, sizeof(mBuffer))) > 0) { //-1 indicates an error
0135         switch (mBuffer[0]) {
0136         case 'N':
0137             if ((unsigned int)size > sizeof("Name:") && qstrncmp(mBuffer, "Name:", sizeof("Name:") - 1) == 0) {
0138                 if (process->command().isEmpty()) {
0139                     process->setName(QString::fromLocal8Bit(mBuffer + sizeof("Name:") - 1, size - sizeof("Name:") + 1).trimmed());
0140                 }
0141                 if (++found == 6) {
0142                     goto finish;
0143                 }
0144             } else if ((unsigned int)size > sizeof("NoNewPrivs:") && qstrncmp(mBuffer, "NoNewPrivs:", sizeof("NoNewPrivs:") - 1) == 0) {
0145                 process->setNoNewPrivileges(atol(mBuffer + sizeof("NoNewPrivs:") - 1));
0146                 if (++found == 6) {
0147                     goto finish;
0148                 }
0149             }
0150             break;
0151         case 'U':
0152             if ((unsigned int)size > sizeof("Uid:") && qstrncmp(mBuffer, "Uid:", sizeof("Uid:") - 1) == 0) {
0153                 qlonglong uid;
0154                 qlonglong euid;
0155                 qlonglong suid;
0156                 qlonglong fsuid;
0157                 sscanf(mBuffer + sizeof("Uid:") - 1, "%lld %lld %lld %lld", &uid, &euid, &suid, &fsuid);
0158                 process->setUid(uid);
0159                 process->setEuid(euid);
0160                 process->setSuid(suid);
0161                 process->setFsuid(fsuid);
0162                 if (++found == 6) {
0163                     goto finish;
0164                 }
0165             }
0166             break;
0167         case 'G':
0168             if ((unsigned int)size > sizeof("Gid:") && qstrncmp(mBuffer, "Gid:", sizeof("Gid:") - 1) == 0) {
0169                 qlonglong gid, egid, sgid, fsgid;
0170                 sscanf(mBuffer + sizeof("Gid:") - 1, "%lld %lld %lld %lld", &gid, &egid, &sgid, &fsgid);
0171                 process->setGid(gid);
0172                 process->setEgid(egid);
0173                 process->setSgid(sgid);
0174                 process->setFsgid(fsgid);
0175                 if (++found == 6) {
0176                     goto finish;
0177                 }
0178             }
0179             break;
0180         case 'T':
0181             if ((unsigned int)size > sizeof("TracerPid:") && qstrncmp(mBuffer, "TracerPid:", sizeof("TracerPid:") - 1) == 0) {
0182                 process->setTracerpid(atol(mBuffer + sizeof("TracerPid:") - 1));
0183                 if (process->tracerpid() == 0) {
0184                     process->setTracerpid(-1);
0185                 }
0186                 if (++found == 6) {
0187                     goto finish;
0188                 }
0189             } else if ((unsigned int)size > sizeof("Threads:") && qstrncmp(mBuffer, "Threads:", sizeof("Threads:") - 1) == 0) {
0190                 process->setNumThreads(atol(mBuffer + sizeof("Threads:") - 1));
0191                 if (++found == 6) {
0192                     goto finish;
0193                 }
0194             }
0195             break;
0196         default:
0197             break;
0198         }
0199     }
0200 
0201 finish:
0202     mFile.close();
0203     return true;
0204 }
0205 
0206 bool ProcessesLocal::Private::readProcCGroup(const QString &dir, Process *process)
0207 {
0208     mFile.setFileName(dir + QStringLiteral("cgroup"));
0209     if (!mFile.open(QIODevice::ReadOnly)) {
0210         return false; /* process has terminated in the meantime */
0211     }
0212 
0213     while (mFile.readLine(mBuffer, sizeof(mBuffer)) > 0) { //-1 indicates an error
0214         if (mBuffer[0] == '0' && mBuffer[1] == ':' && mBuffer[2] == ':') {
0215             process->setCGroup(QString::fromLocal8Bit(&mBuffer[3]).trimmed());
0216             break;
0217         }
0218     }
0219     mFile.close();
0220     return true;
0221 }
0222 
0223 bool ProcessesLocal::Private::readProcAttr(const QString &dir, Process *process)
0224 {
0225     mFile.setFileName(dir + QStringLiteral("attr/current"));
0226     if (!mFile.open(QIODevice::ReadOnly)) {
0227         return false; /* process has terminated in the meantime */
0228     }
0229 
0230     if (mFile.readLine(mBuffer, sizeof(mBuffer)) > 0) { //-1 indicates an error
0231         process->setMACContext(QString::fromLocal8Bit(mBuffer).trimmed());
0232     }
0233     mFile.close();
0234     return true;
0235 }
0236 
0237 long ProcessesLocal::getParentPid(long pid)
0238 {
0239     if (pid <= 0) {
0240         return -1;
0241     }
0242     d->mFile.setFileName(QStringLiteral("/proc/") + QString::number(pid) + QStringLiteral("/stat"));
0243     if (!d->mFile.open(QIODevice::ReadOnly)) {
0244         return -1; /* process has terminated in the meantime */
0245     }
0246 
0247     int size; // amount of data read in
0248     if ((size = d->mFile.readLine(d->mBuffer, sizeof(d->mBuffer))) <= 0) { //-1 indicates nothing read
0249         d->mFile.close();
0250         return -1;
0251     }
0252 
0253     d->mFile.close();
0254     char *word = d->mBuffer;
0255     // The command name is the second parameter, and this ends with a closing bracket.  So find the last
0256     // closing bracket and start from there
0257     word = strrchr(word, ')');
0258     if (!word) {
0259         return -1;
0260     }
0261     word++; // Nove to the space after the last ")"
0262     int current_word = 1;
0263 
0264     while (true) {
0265         if (word[0] == ' ') {
0266             if (++current_word == 3) {
0267                 break;
0268             }
0269         } else if (word[0] == 0) {
0270             return -1; // end of data - serious problem
0271         }
0272         word++;
0273     }
0274     long ppid = atol(++word);
0275     if (ppid == 0) {
0276         return -1;
0277     }
0278     return ppid;
0279 }
0280 
0281 bool ProcessesLocal::Private::readProcStat(const QString &dir, Process *ps)
0282 {
0283     QString filename = dir + QStringLiteral("stat");
0284     // As an optimization, if the last file read in was stat, then we already have this info in memory
0285     if (mFile.fileName() != filename) {
0286         mFile.setFileName(filename);
0287         if (!mFile.open(QIODevice::ReadOnly)) {
0288             return false; /* process has terminated in the meantime */
0289         }
0290         if (mFile.readLine(mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read
0291             mFile.close();
0292             return false;
0293         }
0294         mFile.close();
0295     }
0296 
0297     char *word = mBuffer;
0298     // The command name is the second parameter, and this ends with a closing bracket.  So find the last
0299     // closing bracket and start from there
0300     word = strrchr(word, ')');
0301     if (!word) {
0302         return false;
0303     }
0304     word++; // Nove to the space after the last ")"
0305     int current_word = 1; // We've skipped the process ID and now at the end of the command name
0306     char status = '\0';
0307     unsigned long long vmSize = 0;
0308     unsigned long long vmRSS = 0;
0309     while (current_word < 23) {
0310         if (word[0] == ' ') {
0311             ++current_word;
0312             switch (current_word) {
0313             case 2: // status
0314                 status = word[1]; // Look at the first letter of the status.
0315                 // We analyze this after the while loop
0316                 break;
0317             case 6: // ttyNo
0318             {
0319                 int ttyNo = atoi(word + 1);
0320                 int major = ttyNo >> 8;
0321                 int minor = ttyNo & 0xff;
0322                 switch (major) {
0323                 case 136:
0324                     ps->setTty(QByteArray("pts/") + QByteArray::number(minor));
0325                     break;
0326                 case 5:
0327                     ps->setTty(QByteArray("tty"));
0328                     break;
0329                 case 4:
0330                     if (minor < 64) {
0331                         ps->setTty(QByteArray("tty") + QByteArray::number(minor));
0332                     } else {
0333                         ps->setTty(QByteArray("ttyS") + QByteArray::number(minor - 64));
0334                     }
0335                     break;
0336                 default:
0337                     ps->setTty(QByteArray());
0338                 }
0339             } break;
0340             case 13: // userTime
0341                 ps->setUserTime(atoll(word + 1));
0342                 break;
0343             case 14: // sysTime
0344                 ps->setSysTime(atoll(word + 1));
0345                 break;
0346             case 18: // niceLevel
0347                 ps->setNiceLevel(atoi(word + 1)); /*Or should we use getPriority instead? */
0348                 break;
0349             case 21: // startTime
0350                 ps->setStartTime(atoll(word + 1));
0351                 break;
0352             case 22: // vmSize
0353                 vmSize = atoll(word + 1);
0354                 break;
0355             case 23: // vmRSS
0356                 vmRSS = atoll(word + 1);
0357                 break;
0358             default:
0359                 break;
0360             }
0361         } else if (word[0] == 0) {
0362             return false; // end of data - serious problem
0363         }
0364         word++;
0365     }
0366 
0367     /* There was a "(ps->vmRss+3) * sysconf(_SC_PAGESIZE)" here in the original ksysguard code.  I have no idea why!  After comparing it to
0368      *   meminfo and other tools, this means we report the RSS by 12 bytes differently compared to them.  So I'm removing the +3
0369      *   to be consistent.  NEXT TIME COMMENT STRANGE THINGS LIKE THAT! :-)
0370      *
0371      *   Update: I think I now know why - the kernel allocates 3 pages for
0372      *   tracking information about each the process. This memory isn't
0373      *   included in vmRSS..*/
0374     ps->setVmRSS(vmRSS * (sysconf(_SC_PAGESIZE) / 1024)); /*convert to KiB*/
0375     ps->setVmSize(vmSize / 1024); /* convert to KiB */
0376 
0377     switch (status) {
0378     case 'R':
0379         ps->setStatus(Process::Running);
0380         break;
0381     case 'S':
0382         ps->setStatus(Process::Sleeping);
0383         break;
0384     case 'D':
0385         ps->setStatus(Process::DiskSleep);
0386         break;
0387     case 'Z':
0388         ps->setStatus(Process::Zombie);
0389         break;
0390     case 'T':
0391         ps->setStatus(Process::Stopped);
0392         break;
0393     case 'W':
0394         ps->setStatus(Process::Paging);
0395         break;
0396     default:
0397         ps->setStatus(Process::OtherStatus);
0398         break;
0399     }
0400     return true;
0401 }
0402 
0403 bool ProcessesLocal::Private::readProcStatm(const QString &dir, Process *process)
0404 {
0405 #ifdef _SC_PAGESIZE
0406     mFile.setFileName(dir + QStringLiteral("statm"));
0407     if (!mFile.open(QIODevice::ReadOnly)) {
0408         return false; /* process has terminated in the meantime */
0409     }
0410 
0411     if (mFile.readLine(mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read
0412         mFile.close();
0413         return 0;
0414     }
0415     mFile.close();
0416 
0417     int current_word = 0;
0418     char *word = mBuffer;
0419 
0420     while (true) {
0421         if (word[0] == ' ') {
0422             if (++current_word == 2) {
0423                 // number of pages that are shared
0424                 break;
0425             }
0426         } else if (word[0] == 0) {
0427             return false; // end of data - serious problem
0428         }
0429         word++;
0430     }
0431     long shared = atol(word + 1);
0432 
0433     /* we use the rss - shared  to find the amount of memory just this app uses */
0434     process->setVmURSS(process->vmRSS() - (shared * sysconf(_SC_PAGESIZE) / 1024));
0435 #else
0436     process->setVmURSS(0);
0437 #endif
0438     return true;
0439 }
0440 
0441 bool ProcessesLocal::Private::readProcCmdline(const QString &dir, Process *process)
0442 {
0443     if (!process->command().isNull()) {
0444         return true; // only parse the cmdline once.  This function takes up 25% of the CPU time :-/
0445     }
0446     mFile.setFileName(dir + QStringLiteral("cmdline"));
0447     if (!mFile.open(QIODevice::ReadOnly)) {
0448         return false; /* process has terminated in the meantime */
0449     }
0450 
0451     QTextStream in(&mFile);
0452     process->setCommand(in.readAll());
0453 
0454     // cmdline separates parameters with the NULL character
0455     if (!process->command().isEmpty()) {
0456         // extract non-truncated name from cmdline
0457         int zeroIndex = process->command().indexOf(QLatin1Char('\0'));
0458         int processNameStart = process->command().lastIndexOf(QLatin1Char('/'), zeroIndex);
0459         if (processNameStart == -1) {
0460             processNameStart = 0;
0461         } else {
0462             processNameStart++;
0463         }
0464         QString nameFromCmdLine = process->command().mid(processNameStart, zeroIndex - processNameStart);
0465         if (nameFromCmdLine.startsWith(process->name())) {
0466             process->setName(nameFromCmdLine);
0467         }
0468 
0469         process->command().replace(QLatin1Char('\0'), QLatin1Char(' '));
0470     }
0471 
0472     mFile.close();
0473     return true;
0474 }
0475 
0476 bool ProcessesLocal::Private::getNiceness(long pid, Process *process)
0477 {
0478     int sched = sched_getscheduler(pid);
0479     switch (sched) {
0480     case (SCHED_OTHER):
0481         process->setScheduler(KSysGuard::Process::Other);
0482         break;
0483     case (SCHED_RR):
0484         process->setScheduler(KSysGuard::Process::RoundRobin);
0485         break;
0486     case (SCHED_FIFO):
0487         process->setScheduler(KSysGuard::Process::Fifo);
0488         break;
0489 #ifdef SCHED_IDLE
0490     case (SCHED_IDLE):
0491         process->setScheduler(KSysGuard::Process::SchedulerIdle);
0492         break;
0493 #endif
0494 #ifdef SCHED_BATCH
0495     case (SCHED_BATCH):
0496         process->setScheduler(KSysGuard::Process::Batch);
0497         break;
0498 #endif
0499     default:
0500         process->setScheduler(KSysGuard::Process::Other);
0501     }
0502     if (sched == SCHED_FIFO || sched == SCHED_RR) {
0503         struct sched_param param;
0504         if (sched_getparam(pid, &param) == 0) {
0505             process->setNiceLevel(param.sched_priority);
0506         } else {
0507             process->setNiceLevel(0); // Error getting scheduler parameters.
0508         }
0509     }
0510 
0511 #ifdef HAVE_IONICE
0512     int ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); /* Returns from 0 to 7 for the iopriority, and -1 if there's an error */
0513     if (ioprio == -1) {
0514         process->setIoniceLevel(-1);
0515         process->setIoPriorityClass(KSysGuard::Process::None);
0516         return false; /* Error. Just give up. */
0517     }
0518     process->setIoniceLevel(ioprio & 0xff); /* Bottom few bits are the priority */
0519     process->setIoPriorityClass((KSysGuard::Process::IoPriorityClass)(ioprio >> IOPRIO_CLASS_SHIFT)); /* Top few bits are the class */
0520     return true;
0521 #else
0522     return false; /* Do nothing, if we do not support this architecture */
0523 #endif
0524 }
0525 
0526 bool ProcessesLocal::Private::getIOStatistics(const QString &dir, Process *process)
0527 {
0528     QString filename = dir + QStringLiteral("io");
0529     // As an optimization, if the last file read in was io, then we already have this info in memory
0530     mFile.setFileName(filename);
0531     if (!mFile.open(QIODevice::ReadOnly)) {
0532         return false; /* process has terminated in the meantime */
0533     }
0534     if (mFile.read(mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read
0535         mFile.close();
0536         return false;
0537     }
0538     mFile.close();
0539 
0540     int current_word = 0; // count from 0
0541     char *word = mBuffer;
0542     while (current_word < 6 && word[0] != 0) {
0543         if (word[0] == ' ') {
0544             qlonglong number = atoll(word + 1);
0545             switch (current_word++) {
0546             case 0: // rchar - characters read
0547                 process->setIoCharactersRead(number);
0548                 break;
0549             case 1: // wchar - characters written
0550                 process->setIoCharactersWritten(number);
0551                 break;
0552             case 2: // syscr - read syscall
0553                 process->setIoReadSyscalls(number);
0554                 break;
0555             case 3: // syscw - write syscall
0556                 process->setIoWriteSyscalls(number);
0557                 break;
0558             case 4: // read_bytes - bytes actually read from I/O
0559                 process->setIoCharactersActuallyRead(number);
0560                 break;
0561             case 5: // write_bytes - bytes actually written to I/O
0562                 process->setIoCharactersActuallyWritten(number);
0563             default:
0564                 break;
0565             }
0566         }
0567         word++;
0568     }
0569     return true;
0570 }
0571 
0572 bool ProcessesLocal::updateProcessInfo(long pid, Process *process)
0573 {
0574     bool success = true;
0575     const QString dir = QLatin1String("/proc/") + QString::number(pid) + QLatin1Char('/');
0576 
0577     if (mUpdateFlags.testFlag(Processes::Smaps)) {
0578         auto runnable = new ReadProcSmapsRunnable{dir};
0579 
0580         connect(runnable, &ReadProcSmapsRunnable::finished, this, [this, pid](qulonglong pss) {
0581             Q_EMIT processUpdated(pid, {{Process::VmPSS, pss}});
0582         });
0583 
0584         QThreadPool::globalInstance()->start(runnable);
0585     }
0586 
0587     if (!d->readProcStat(dir, process)) {
0588         success = false;
0589     }
0590     if (!d->readProcStatus(dir, process)) {
0591         success = false;
0592     }
0593     if (!d->readProcStatm(dir, process)) {
0594         success = false;
0595     }
0596     if (!d->readProcCmdline(dir, process)) {
0597         success = false;
0598     }
0599     if (!d->readProcCGroup(dir, process)) {
0600         success = false;
0601     }
0602     if (!d->readProcAttr(dir, process)) {
0603         success = false;
0604     }
0605     if (!d->getNiceness(pid, process)) {
0606         success = false;
0607     }
0608     if (mUpdateFlags.testFlag(Processes::IOStatistics) && !d->getIOStatistics(dir, process)) {
0609         success = false;
0610     }
0611 
0612     return success;
0613 }
0614 
0615 QSet<long> ProcessesLocal::getAllPids()
0616 {
0617     QSet<long> pids;
0618     if (d->mProcDir == nullptr) {
0619         return pids; // There's not much we can do without /proc
0620     }
0621     struct dirent *entry;
0622     rewinddir(d->mProcDir);
0623     while ((entry = readdir(d->mProcDir))) {
0624         if (entry->d_name[0] >= '0' && entry->d_name[0] <= '9') {
0625             pids.insert(atol(entry->d_name));
0626         }
0627     }
0628     return pids;
0629 }
0630 
0631 Processes::Error ProcessesLocal::sendSignal(long pid, int sig)
0632 {
0633     errno = 0;
0634     if (pid <= 0) {
0635         return Processes::InvalidPid;
0636     }
0637     if (kill((pid_t)pid, sig)) {
0638         switch (errno) {
0639         case ESRCH:
0640             return Processes::ProcessDoesNotExistOrZombie;
0641         case EINVAL:
0642             return Processes::InvalidParameter;
0643         case EPERM:
0644             return Processes::InsufficientPermissions;
0645         }
0646         // Kill failed
0647         return Processes::Unknown;
0648     }
0649     return Processes::NoError;
0650 }
0651 
0652 Processes::Error ProcessesLocal::setNiceness(long pid, int priority)
0653 {
0654     errno = 0;
0655     if (pid <= 0) {
0656         return Processes::InvalidPid;
0657     }
0658     auto error = [] {
0659         switch (errno) {
0660         case ESRCH:
0661         case ENOENT:
0662             return Processes::ProcessDoesNotExistOrZombie;
0663         case EINVAL:
0664             return Processes::InvalidParameter;
0665         case EACCES:
0666         case EPERM:
0667             return Processes::InsufficientPermissions;
0668         default:
0669             return Processes::Unknown;
0670         }
0671     };
0672     auto threadList{QDir(QString::fromLatin1("/proc/%1/task").arg(pid)).entryList(QDir::NoDotAndDotDot | QDir::Dirs)};
0673     if (threadList.isEmpty()) {
0674         return error();
0675     }
0676     for (auto entry : threadList) {
0677         int threadId = entry.toInt();
0678         if (!threadId) {
0679             return Processes::InvalidParameter;
0680         }
0681         if (setpriority(PRIO_PROCESS, threadId, priority)) {
0682             return error();
0683         }
0684     }
0685     return Processes::NoError;
0686 }
0687 
0688 Processes::Error ProcessesLocal::setScheduler(long pid, int priorityClass, int priority)
0689 {
0690     errno = 0;
0691     if (priorityClass == KSysGuard::Process::Other || priorityClass == KSysGuard::Process::Batch || priorityClass == KSysGuard::Process::SchedulerIdle) {
0692         priority = 0;
0693     }
0694     if (pid <= 0) {
0695         return Processes::InvalidPid;
0696     }
0697     struct sched_param params;
0698     params.sched_priority = priority;
0699     int policy;
0700     switch (priorityClass) {
0701     case (KSysGuard::Process::Other):
0702         policy = SCHED_OTHER;
0703         break;
0704     case (KSysGuard::Process::RoundRobin):
0705         policy = SCHED_RR;
0706         break;
0707     case (KSysGuard::Process::Fifo):
0708         policy = SCHED_FIFO;
0709         break;
0710 #ifdef SCHED_IDLE
0711     case (KSysGuard::Process::SchedulerIdle):
0712         policy = SCHED_IDLE;
0713         break;
0714 #endif
0715 #ifdef SCHED_BATCH
0716     case (KSysGuard::Process::Batch):
0717         policy = SCHED_BATCH;
0718         break;
0719 #endif
0720     default:
0721         return Processes::NotSupported;
0722     }
0723 
0724     auto error = [] {
0725         switch (errno) {
0726         case ESRCH:
0727         case ENOENT:
0728             return Processes::ProcessDoesNotExistOrZombie;
0729         case EINVAL:
0730             return Processes::InvalidParameter;
0731         case EPERM:
0732             return Processes::InsufficientPermissions;
0733         default:
0734             return Processes::Unknown;
0735         }
0736     };
0737     auto threadList{QDir(QString::fromLatin1("/proc/%1/task").arg(pid)).entryList(QDir::NoDotAndDotDot | QDir::Dirs)};
0738     if (threadList.isEmpty()) {
0739         return error();
0740     }
0741     for (auto entry : threadList) {
0742         int threadId = entry.toInt();
0743         if (!threadId) {
0744             return Processes::InvalidParameter;
0745         }
0746         if (sched_setscheduler(threadId, policy, &params) != 0) {
0747             return error();
0748         }
0749     }
0750     return Processes::NoError;
0751 }
0752 
0753 Processes::Error ProcessesLocal::setIoNiceness(long pid, int priorityClass, int priority)
0754 {
0755     errno = 0;
0756     if (pid <= 0) {
0757         return Processes::InvalidPid;
0758     }
0759 #ifdef HAVE_IONICE
0760     if (ioprio_set(IOPRIO_WHO_PROCESS, pid, priority | priorityClass << IOPRIO_CLASS_SHIFT) == -1) {
0761         // set io niceness failed
0762         switch (errno) {
0763         case ESRCH:
0764             return Processes::ProcessDoesNotExistOrZombie;
0765             break;
0766         case EINVAL:
0767             return Processes::InvalidParameter;
0768         case EPERM:
0769             return Processes::InsufficientPermissions;
0770         }
0771         return Processes::Unknown;
0772     }
0773     return Processes::NoError;
0774 #else
0775     return Processes::NotSupported;
0776 #endif
0777 }
0778 
0779 bool ProcessesLocal::supportsIoNiceness()
0780 {
0781 #ifdef HAVE_IONICE
0782     return true;
0783 #else
0784     return false;
0785 #endif
0786 }
0787 
0788 long long ProcessesLocal::totalPhysicalMemory()
0789 {
0790     // Try to get the memory via sysconf.  Note the cast to long long to try to avoid a long overflow
0791     // Should we use sysconf(_SC_PAGESIZE)  or getpagesize()  ?
0792 #ifdef _SC_PHYS_PAGES
0793     return ((long long)sysconf(_SC_PHYS_PAGES)) * (sysconf(_SC_PAGESIZE) / 1024);
0794 #else
0795     // This is backup code in case this is not defined.  It should never fail on a linux system.
0796 
0797     d->mFile.setFileName("/proc/meminfo");
0798     if (!d->mFile.open(QIODevice::ReadOnly)) {
0799         return 0;
0800     }
0801 
0802     int size;
0803     while ((size = d->mFile.readLine(d->mBuffer, sizeof(d->mBuffer))) > 0) { //-1 indicates an error
0804         switch (d->mBuffer[0]) {
0805         case 'M':
0806             if ((unsigned int)size > sizeof("MemTotal:") && qstrncmp(d->mBuffer, "MemTotal:", sizeof("MemTotal:") - 1) == 0) {
0807                 d->mFile.close();
0808                 return atoll(d->mBuffer + sizeof("MemTotal:") - 1);
0809             }
0810         }
0811     }
0812     return 0; // Not found.  Probably will never happen
0813 #endif
0814 }
0815 ProcessesLocal::~ProcessesLocal()
0816 {
0817     delete d;
0818 }
0819 
0820 }