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

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