File indexing completed on 2024-04-21 05:51:25

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.countm>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Config
0008 #include "config-konsole.h"
0009 
0010 // Own
0011 #include "NullProcessInfo.h"
0012 #include "ProcessInfo.h"
0013 #include "UnixProcessInfo.h"
0014 
0015 // Unix
0016 #ifndef Q_OS_WIN
0017 #include <arpa/inet.h>
0018 #include <cerrno>
0019 #include <netinet/in.h>
0020 #include <pwd.h>
0021 #include <sys/param.h>
0022 #include <sys/socket.h>
0023 #include <unistd.h>
0024 #endif
0025 
0026 // Qt
0027 #include <QDir>
0028 #include <QFileInfo>
0029 #include <QHostInfo>
0030 #include <QStringList>
0031 #include <QTextStream>
0032 #include <QtGlobal>
0033 
0034 // KDE
0035 #include <KConfigGroup>
0036 #include <KSharedConfig>
0037 #include <KUser>
0038 
0039 #if defined(Q_OS_LINUX) || defined(Q_OS_SOLARIS)
0040 #include <memory>
0041 #endif
0042 
0043 #if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) || defined(Q_OS_MACOS)
0044 #include <QSharedPointer>
0045 #include <sys/sysctl.h>
0046 #endif
0047 
0048 #if defined(Q_OS_MACOS)
0049 #include <libproc.h>
0050 #include <qplatformdefs.h>
0051 #endif
0052 
0053 #if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
0054 #include <sys/types.h>
0055 
0056 #include <sys/syslimits.h>
0057 #include <sys/user.h>
0058 #if defined(Q_OS_FREEBSD)
0059 #include <libutil.h>
0060 #include <sys/param.h>
0061 #include <sys/queue.h>
0062 #endif
0063 #endif
0064 
0065 using namespace Konsole;
0066 
0067 ProcessInfo::ProcessInfo(int pid)
0068     : _fields(ARGUMENTS) // arguments
0069     // are currently always valid,
0070     // they just return an empty
0071     // vector / map respectively
0072     // if no arguments
0073     // have been explicitly set
0074     , _pid(pid)
0075     , _parentPid(0)
0076     , _foregroundPid(0)
0077     , _userId(0)
0078     , _lastError(NoError)
0079     , _name(QString())
0080     , _userName(QString())
0081     , _userHomeDir(QString())
0082     , _currentDir(QString())
0083     , _userNameRequired(true)
0084     , _arguments(QVector<QString>())
0085 {
0086 }
0087 
0088 ProcessInfo::Error ProcessInfo::error() const
0089 {
0090     return _lastError;
0091 }
0092 
0093 void ProcessInfo::setError(Error error)
0094 {
0095     _lastError = error;
0096 }
0097 
0098 void ProcessInfo::update()
0099 {
0100     readCurrentDir(_pid);
0101     readProcessName(_pid);
0102 }
0103 
0104 void ProcessInfo::refreshArguments()
0105 {
0106     clearArguments();
0107     readArguments(_pid);
0108 }
0109 
0110 QString ProcessInfo::validCurrentDir() const
0111 {
0112     bool ok = false;
0113 
0114     // read current dir, if an error occurs try the parent as the next
0115     // best option
0116     int currentPid = parentPid(&ok);
0117     QString dir = currentDir(&ok);
0118     while (!ok && currentPid != 0) {
0119         ProcessInfo *current = ProcessInfo::newInstance(currentPid);
0120         current->update();
0121         currentPid = current->parentPid(&ok);
0122         dir = current->currentDir(&ok);
0123         delete current;
0124     }
0125 
0126     return dir;
0127 }
0128 
0129 QStringList ProcessInfo::_commonDirNames;
0130 
0131 QStringList ProcessInfo::commonDirNames()
0132 {
0133     static bool forTheFirstTime = true;
0134 
0135     if (forTheFirstTime) {
0136         const KSharedConfigPtr &config = KSharedConfig::openConfig();
0137         const KConfigGroup &configGroup = config->group(QStringLiteral("ProcessInfo"));
0138         // Need to make a local copy so the begin() and end() point to the same QList
0139         _commonDirNames = configGroup.readEntry("CommonDirNames", QStringList());
0140         _commonDirNames.removeDuplicates();
0141 
0142         forTheFirstTime = false;
0143     }
0144 
0145     return _commonDirNames;
0146 }
0147 
0148 QString ProcessInfo::formatShortDir(const QString &input) const
0149 {
0150     if (input == QLatin1Char('/')) {
0151         return QStringLiteral("/");
0152     }
0153 
0154     QString result;
0155 
0156     const QStringList parts = input.split(QDir::separator());
0157 
0158     QStringList dirNamesToShorten = commonDirNames();
0159 
0160     // go backwards through the list of the path's parts
0161     // adding abbreviations of common directory names
0162     // and stopping when we reach a dir name which is not
0163     // in the commonDirNames set
0164     for (auto it = parts.crbegin(), endIt = parts.crend(); it != endIt; ++it) {
0165         const QString &part = *it;
0166         if (dirNamesToShorten.contains(part)) {
0167             result.prepend(QDir::separator() + static_cast<QString>(part[0]));
0168         } else {
0169             result.prepend(part);
0170             break;
0171         }
0172     }
0173 
0174     return result;
0175 }
0176 
0177 QVector<QString> ProcessInfo::arguments(bool *ok) const
0178 {
0179     *ok = _fields.testFlag(ARGUMENTS);
0180 
0181     return _arguments;
0182 }
0183 
0184 bool ProcessInfo::isValid() const
0185 {
0186     return _fields.testFlag(PROCESS_ID);
0187 }
0188 
0189 int ProcessInfo::pid(bool *ok) const
0190 {
0191     *ok = _fields.testFlag(PROCESS_ID);
0192 
0193     return _pid;
0194 }
0195 
0196 int ProcessInfo::parentPid(bool *ok) const
0197 {
0198     *ok = _fields.testFlag(PARENT_PID);
0199 
0200     return _parentPid;
0201 }
0202 
0203 int ProcessInfo::foregroundPid(bool *ok) const
0204 {
0205     *ok = _fields.testFlag(FOREGROUND_PID);
0206 
0207     return _foregroundPid;
0208 }
0209 
0210 QString ProcessInfo::name(bool *ok) const
0211 {
0212     *ok = _fields.testFlag(NAME);
0213 
0214     return _name;
0215 }
0216 
0217 int ProcessInfo::userId(bool *ok) const
0218 {
0219     *ok = _fields.testFlag(UID);
0220 
0221     return _userId;
0222 }
0223 
0224 QString ProcessInfo::userName() const
0225 {
0226     return _userName;
0227 }
0228 
0229 QString ProcessInfo::userHomeDir() const
0230 {
0231     return _userHomeDir;
0232 }
0233 
0234 QString ProcessInfo::localHost()
0235 {
0236     return QHostInfo::localHostName();
0237 }
0238 
0239 void ProcessInfo::setPid(int pid)
0240 {
0241     _pid = pid;
0242     _fields |= PROCESS_ID;
0243 }
0244 
0245 void ProcessInfo::setUserId(int uid)
0246 {
0247     _userId = uid;
0248     _fields |= UID;
0249 }
0250 
0251 void ProcessInfo::setUserName(const QString &name)
0252 {
0253     _userName = name;
0254     setUserHomeDir();
0255 }
0256 
0257 void ProcessInfo::setUserHomeDir()
0258 {
0259     const QString &usersName = userName();
0260     if (!usersName.isEmpty()) {
0261         _userHomeDir = KUser(usersName).homeDir();
0262     } else {
0263         _userHomeDir = QDir::homePath();
0264     }
0265 }
0266 
0267 void ProcessInfo::setParentPid(int pid)
0268 {
0269     _parentPid = pid;
0270     _fields |= PARENT_PID;
0271 }
0272 
0273 void ProcessInfo::setForegroundPid(int pid)
0274 {
0275     _foregroundPid = pid;
0276     _fields |= FOREGROUND_PID;
0277 }
0278 
0279 void ProcessInfo::setUserNameRequired(bool need)
0280 {
0281     _userNameRequired = need;
0282 }
0283 
0284 bool ProcessInfo::userNameRequired() const
0285 {
0286     return _userNameRequired;
0287 }
0288 
0289 QString ProcessInfo::currentDir(bool *ok) const
0290 {
0291     if (ok != nullptr) {
0292         *ok = (_fields & CURRENT_DIR) != 0;
0293     }
0294 
0295     return _currentDir;
0296 }
0297 
0298 void ProcessInfo::setCurrentDir(const QString &dir)
0299 {
0300     _fields |= CURRENT_DIR;
0301     _currentDir = dir;
0302 }
0303 
0304 void ProcessInfo::setName(const QString &name)
0305 {
0306     _name = name;
0307     _fields |= NAME;
0308 }
0309 
0310 void ProcessInfo::addArgument(const QString &argument)
0311 {
0312     _arguments << argument;
0313 }
0314 
0315 void ProcessInfo::clearArguments()
0316 {
0317     _arguments.clear();
0318 }
0319 
0320 void ProcessInfo::setFileError(QFile::FileError error)
0321 {
0322     switch (error) {
0323     case QFile::PermissionsError:
0324         setError(ProcessInfo::PermissionsError);
0325         break;
0326     case QFile::NoError:
0327         setError(ProcessInfo::NoError);
0328         break;
0329     default:
0330         setError(ProcessInfo::UnknownError);
0331     }
0332 }
0333 
0334 #if defined(Q_OS_LINUX)
0335 #include <QByteArray>
0336 #include <QDBusArgument>
0337 #include <QDBusConnection>
0338 #include <QDBusConnectionInterface>
0339 #include <QDBusMessage>
0340 #include <QDBusMetaType>
0341 #include <QFile>
0342 #include <QMap>
0343 #include <QString>
0344 #include <QThread>
0345 #include <QVariant>
0346 
0347 #include "KonsoleSettings.h"
0348 
0349 typedef QPair<QString, QDBusVariant> VariantPair;
0350 typedef QList<VariantPair> VariantList;
0351 typedef QList<QPair<QString, VariantList>> EmptyArray;
0352 
0353 Q_DECLARE_METATYPE(VariantPair);
0354 Q_DECLARE_METATYPE(VariantList);
0355 Q_DECLARE_METATYPE(EmptyArray);
0356 
0357 // EmptyArray is a custom type used for the last argument of org.freedesktop.systemd1.Manager.StartTransientUnit
0358 
0359 QDBusArgument &operator<<(QDBusArgument &argument, const VariantPair pair)
0360 {
0361     argument.beginStructure();
0362     argument << pair.first;
0363     argument << pair.second;
0364     argument.endStructure();
0365 
0366     return argument;
0367 }
0368 
0369 const QDBusArgument &operator>>(const QDBusArgument &argument, VariantPair &pair)
0370 {
0371     argument.beginStructure();
0372     argument >> pair.first;
0373     QVariant value;
0374     argument >> value;
0375     pair.second = qvariant_cast<QDBusVariant>(value);
0376     argument.endStructure();
0377 
0378     return argument;
0379 }
0380 
0381 QDBusArgument &operator<<(QDBusArgument &argument, const QPair<QString, VariantList> pair)
0382 {
0383     argument.beginStructure();
0384     argument << pair.first;
0385     argument << pair.second;
0386     argument.endStructure();
0387 
0388     return argument;
0389 }
0390 
0391 const QDBusArgument &operator>>(const QDBusArgument &argument, QPair<QString, VariantList> &pair)
0392 {
0393     argument.beginStructure();
0394     argument >> pair.first;
0395     VariantList variantList;
0396     argument >> variantList;
0397     pair.second = variantList;
0398     argument.endStructure();
0399 
0400     return argument;
0401 }
0402 
0403 class LinuxProcessInfo : public UnixProcessInfo
0404 {
0405 public:
0406     explicit LinuxProcessInfo(int pid, int sessionPid)
0407         : UnixProcessInfo(pid)
0408     {
0409         if (pid == 0 || _cGroupCreationFailed) {
0410             return;
0411         }
0412 
0413         if (_createdAppCGroupPath.isEmpty() && !_cGroupCreationFailed) {
0414             _cGroupCreationFailed = KonsoleSettings::enableMemoryMonitoring() && !initCGroupHierachy(pid);
0415             return;
0416         }
0417 
0418         const bool isForegroundProcess = sessionPid != -1;
0419 
0420         if (!isForegroundProcess) {
0421             _cGroupCreationFailed = !createCGroup(QStringLiteral("tab(%1).scope").arg(pid), pid);
0422         } else {
0423             _cGroupCreationFailed = !moveToCGroup(getProcCGroup(sessionPid), pid);
0424         }
0425     }
0426 
0427     static bool setUnitMemLimit(const int newMemHigh)
0428     {
0429         QFile memHighFile(_createdAppCGroupPath + QDir::separator() + QStringLiteral("memory.high"));
0430 
0431         if (_cGroupCreationFailed || !memHighFile.open(QIODevice::WriteOnly)) {
0432             return false;
0433         }
0434 
0435         memHighFile.write(QStringLiteral("%1M").arg(newMemHigh).toLocal8Bit());
0436 
0437         return true;
0438     }
0439 
0440 protected:
0441     bool readCurrentDir(int pid) override
0442     {
0443         char path_buffer[MAXPATHLEN + 1];
0444         path_buffer[MAXPATHLEN] = 0;
0445         QByteArray procCwd = QFile::encodeName(QStringLiteral("/proc/%1/cwd").arg(pid));
0446         const auto length = static_cast<int>(readlink(procCwd.constData(), path_buffer, MAXPATHLEN));
0447         if (length == -1) {
0448             setError(UnknownError);
0449             return false;
0450         }
0451 
0452         path_buffer[length] = '\0';
0453         QString path = QFile::decodeName(path_buffer);
0454 
0455         setCurrentDir(path);
0456         return true;
0457     }
0458 
0459     bool readProcessName(int pid) override
0460     {
0461         Q_UNUSED(pid);
0462 
0463         if (!_infoFile || _infoFile->error() != 0 || !_infoFile->reset()) {
0464             return false;
0465         }
0466 
0467         const QString data = QString::fromUtf8(_infoFile->readLine());
0468 
0469         if (data.isEmpty()) {
0470             setName(data);
0471             return false;
0472         }
0473 
0474         const int nameStartIdx = data.indexOf(QLatin1Char('(')) + 1;
0475         const int nameLength = data.lastIndexOf(QLatin1Char(')')) - nameStartIdx;
0476 
0477         setName(data.mid(nameStartIdx, nameLength));
0478         return true;
0479     }
0480 
0481 private:
0482     bool readProcInfo(int pid) override
0483     {
0484         // indices of various fields within the process status file which
0485         // contain various information about the process
0486         const int PARENT_PID_FIELD = 3;
0487         const int PROCESS_NAME_FIELD = 1;
0488         const int GROUP_PROCESS_FIELD = 7;
0489 
0490         QString parentPidString;
0491         QString processNameString;
0492         QString foregroundPidString;
0493         QString uidLine;
0494         QString uidString;
0495         QStringList uidStrings;
0496 
0497         // For user id read process status file ( /proc/<pid>/status )
0498         //  Can not use getuid() due to it does not work for 'su'
0499         QFile statusInfo(QStringLiteral("/proc/%1/status").arg(pid));
0500         if (statusInfo.open(QIODevice::ReadOnly)) {
0501             QTextStream stream(&statusInfo);
0502             QString statusLine;
0503             do {
0504                 statusLine = stream.readLine();
0505                 if (statusLine.startsWith(QLatin1String("Uid:"))) {
0506                     uidLine = statusLine;
0507                 }
0508             } while (!statusLine.isNull() && uidLine.isNull());
0509 
0510             uidStrings << uidLine.split(QLatin1Char('\t'), Qt::SkipEmptyParts);
0511             // Must be 5 entries: 'Uid: %d %d %d %d' and
0512             // uid string must be less than 5 chars (uint)
0513             if (uidStrings.size() == 5) {
0514                 uidString = uidStrings[1];
0515             }
0516             if (uidString.size() > 5) {
0517                 uidString.clear();
0518             }
0519 
0520             bool ok = false;
0521             const int uid = uidString.toInt(&ok);
0522             if (ok) {
0523                 setUserId(uid);
0524             }
0525             // This will cause constant opening of /etc/passwd
0526             if (userNameRequired()) {
0527                 readUserName();
0528                 setUserNameRequired(false);
0529             }
0530         } else {
0531             setFileError(statusInfo.error());
0532             return false;
0533         }
0534 
0535         // read process status file ( /proc/<pid/stat )
0536         //
0537         // the expected file format is a list of fields separated by spaces, using
0538         // parentheses to escape fields such as the process name which may itself contain
0539         // spaces:
0540         //
0541         // FIELD FIELD (FIELD WITH SPACES) FIELD FIELD
0542         //
0543         _infoFile = std::make_unique<QFile>(new QFile());
0544         _infoFile->setFileName(QStringLiteral("/proc/%1/stat").arg(pid));
0545         if (_infoFile->open(QIODevice::ReadOnly)) {
0546             QTextStream stream(_infoFile.get());
0547             const QString &data = stream.readAll();
0548 
0549             int field = 0;
0550             int pos = 0;
0551 
0552             while (pos < data.length()) {
0553                 QChar c = data[pos];
0554 
0555                 if (c == QLatin1Char(' ')) {
0556                     field++;
0557                 } else {
0558                     switch (field) {
0559                     case PARENT_PID_FIELD:
0560                         parentPidString.append(c);
0561                         break;
0562                     case PROCESS_NAME_FIELD: {
0563                         pos++;
0564                         const int NAME_MAX_LEN = 16;
0565                         const int nameEndIdx = data.lastIndexOf(QStringLiteral(")"), pos + NAME_MAX_LEN);
0566                         processNameString = data.mid(pos, nameEndIdx - pos);
0567                         pos = nameEndIdx;
0568                         break;
0569                     }
0570                     case GROUP_PROCESS_FIELD:
0571                         foregroundPidString.append(c);
0572                         break;
0573                     }
0574                 }
0575 
0576                 pos++;
0577             }
0578         } else {
0579             setFileError(_infoFile->error());
0580             return false;
0581         }
0582 
0583         // check that data was read successfully
0584         bool ok = false;
0585         const int foregroundPid = foregroundPidString.toInt(&ok);
0586         if (ok) {
0587             setForegroundPid(foregroundPid);
0588         }
0589 
0590         const int parentPid = parentPidString.toInt(&ok);
0591         if (ok) {
0592             setParentPid(parentPid);
0593         }
0594 
0595         if (!processNameString.isEmpty()) {
0596             setName(processNameString);
0597         }
0598 
0599         // update object state
0600         setPid(pid);
0601 
0602         return ok;
0603     }
0604 
0605     QDBusMessage callSmdDBus(const QString &objectPath, const QString &interfaceName, const QString &methodName, const QList<QVariant> &args)
0606     {
0607         const QString service(QStringLiteral("org.freedesktop.systemd1"));
0608         const QString interface(service + QLatin1Char('.') + interfaceName);
0609         auto methodCall = QDBusMessage::createMethodCall(service, objectPath, interface, methodName);
0610         methodCall.setArguments(args);
0611 
0612         return QDBusConnection::sessionBus().call(methodCall);
0613     }
0614 
0615     bool initCGroupHierachy(int pid)
0616     {
0617         qDBusRegisterMetaType<VariantPair>();
0618         qDBusRegisterMetaType<VariantList>();
0619         qDBusRegisterMetaType<QPair<QString, VariantList>>();
0620         qDBusRegisterMetaType<EmptyArray>();
0621 
0622         const QString managerObjPath(QStringLiteral("/org/freedesktop/systemd1"));
0623         const QString appUnitName(QStringLiteral("transientKonsole.scope"));
0624 
0625         // check if systemd dbus services exist
0626         if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.systemd1"))) {
0627             return false;
0628         }
0629 
0630         // get current application cgroup path
0631         const QString oldAppCGroupPath(getProcCGroup(getpid()));
0632 
0633         // create application unit
0634         VariantList properties;
0635         const QList<uint> mainPid({static_cast<quint32>(getpid())});
0636 
0637         properties.append(VariantPair({QStringLiteral("Delegate"), QDBusVariant(QVariant::fromValue(true))}));
0638         properties.append(VariantPair({QStringLiteral("ManagedOOMMemoryPressure"), QDBusVariant(QVariant::fromValue(QStringLiteral("kill")))}));
0639         properties.append(VariantPair({QStringLiteral("PIDs"), QDBusVariant(QVariant::fromValue(mainPid))}));
0640 
0641         if (!createSystemdUnit(appUnitName, properties)) {
0642             return false;
0643         }
0644 
0645         // get created app cgroup path
0646         while (getProcCGroup(getpid()) == oldAppCGroupPath) {
0647             QThread::msleep(100); // wait for new unit to be created
0648         }
0649 
0650         _createdAppCGroupPath = getProcCGroup(getpid());
0651 
0652         // create sub cgroups
0653         if (!createCGroup(QStringLiteral("main.scope"), getpid()) || !createCGroup(QStringLiteral("tab(%1).scope").arg(pid), pid)) {
0654             return false;
0655         }
0656 
0657         // enable all controllers
0658         QFile appCGroupContsFile(_createdAppCGroupPath + QStringLiteral("/cgroup.controllers"));
0659         QFile appCGroupSTContsFile(_createdAppCGroupPath + QStringLiteral("/cgroup.subtree_control"));
0660 
0661         if (!appCGroupContsFile.open(QIODevice::ReadOnly) || !appCGroupSTContsFile.open(QIODevice::WriteOnly)) {
0662             setFileError(static_cast<QFile::FileError>(appCGroupContsFile.error() | appCGroupSTContsFile.error()));
0663             return false;
0664         }
0665 
0666         const QStringList conts(QString::fromUtf8(appCGroupContsFile.readAll()).split(QLatin1Char(' ')));
0667         QString contsToEnable;
0668 
0669         for (auto cont : conts) {
0670             contsToEnable += QLatin1Char('+') + cont.simplified();
0671             if (!conts.endsWith(cont)) {
0672                 contsToEnable += QLatin1Char(' ');
0673             }
0674         }
0675 
0676         appCGroupSTContsFile.write(contsToEnable.toLocal8Bit());
0677 
0678         return setUnitMemLimit(KonsoleSettings::memoryLimitValue());
0679     }
0680 
0681     QString getProcCGroup(const int pid)
0682     {
0683         const QString cGroupFilePath(QStringLiteral("/proc/%1/cgroup").arg(pid));
0684         QFile cGroupFile(cGroupFilePath);
0685 
0686         if (!cGroupFile.open(QIODevice::ReadOnly)) {
0687             setFileError(cGroupFile.error());
0688             return QString();
0689         }
0690 
0691         const QString data = QString::fromUtf8(cGroupFile.readAll());
0692 
0693         const QString cGroupPath(data.mid(data.lastIndexOf(QLatin1Char(':')) + 1));
0694 
0695         return QString(QStringLiteral("/sys/fs/cgroup") + cGroupPath).trimmed();
0696     }
0697 
0698     bool createSystemdUnit(const QString &name, const VariantList &propList)
0699     {
0700         const QList<QVariant> args({name, QStringLiteral("fail"), QVariant::fromValue(propList), QVariant::fromValue(EmptyArray())});
0701 
0702         return callSmdDBus(QStringLiteral("/org/freedesktop/systemd1"), QStringLiteral("Manager"), QStringLiteral("StartTransientUnit"), args).type()
0703             != QDBusMessage::ErrorMessage;
0704     }
0705 
0706     bool createCGroup(const QString &name, int initialPid)
0707     {
0708         const QString newCGroupPath(_createdAppCGroupPath + QLatin1Char('/') + name);
0709 
0710         QDir::root().mkpath(newCGroupPath);
0711 
0712         return moveToCGroup(newCGroupPath, initialPid);
0713     }
0714 
0715     bool moveToCGroup(const QString &cGroupPath, int pid)
0716     {
0717         QFile cGroupProcs(cGroupPath + QStringLiteral("/cgroup.procs"));
0718 
0719         if (!cGroupProcs.open(QIODevice::WriteOnly)) {
0720             setFileError(cGroupProcs.error());
0721             return false;
0722         }
0723 
0724         cGroupProcs.write(QStringLiteral("%1").arg(pid).toLocal8Bit());
0725 
0726         return true;
0727     }
0728 
0729     static QString _createdAppCGroupPath;
0730     static bool _cGroupCreationFailed;
0731     std::unique_ptr<QFile> _infoFile;
0732 };
0733 
0734 QString LinuxProcessInfo::_createdAppCGroupPath = QString();
0735 bool LinuxProcessInfo::_cGroupCreationFailed = false;
0736 
0737 #elif defined(Q_OS_FREEBSD)
0738 class FreeBSDProcessInfo : public UnixProcessInfo
0739 {
0740 public:
0741     explicit FreeBSDProcessInfo(int pid)
0742         : UnixProcessInfo(pid)
0743     {
0744     }
0745 
0746 protected:
0747     bool readCurrentDir(int pid) override
0748     {
0749 #if HAVE_OS_DRAGONFLYBSD
0750         int managementInfoBase[4];
0751         char buf[PATH_MAX];
0752         size_t len;
0753 
0754         managementInfoBase[0] = CTL_KERN;
0755         managementInfoBase[1] = KERN_PROC;
0756         managementInfoBase[2] = KERN_PROC_CWD;
0757         managementInfoBase[3] = pid;
0758 
0759         len = sizeof(buf);
0760         if (sysctl(managementInfoBase, 4, buf, &len, NULL, 0) == -1) {
0761             return false;
0762         }
0763 
0764         setCurrentDir(QString::fromUtf8(buf));
0765 
0766         return true;
0767 #else
0768         int numrecords;
0769         struct kinfo_file *info = nullptr;
0770 
0771         info = kinfo_getfile(pid, &numrecords);
0772 
0773         if (!info) {
0774             return false;
0775         }
0776 
0777         for (int i = 0; i < numrecords; ++i) {
0778             if (info[i].kf_fd == KF_FD_TYPE_CWD) {
0779                 setCurrentDir(QString::fromUtf8(info[i].kf_path));
0780 
0781                 free(info);
0782                 return true;
0783             }
0784         }
0785 
0786         free(info);
0787         return false;
0788 #endif
0789     }
0790 
0791     bool readProcessName(int pid) override
0792     {
0793         int managementInfoBase[4];
0794 
0795         managementInfoBase[0] = CTL_KERN;
0796         managementInfoBase[1] = KERN_PROC;
0797         managementInfoBase[2] = KERN_PROC_ARGS;
0798         managementInfoBase[3] = pid;
0799 
0800         auto kInfoProc = getProcInfoStruct(managementInfoBase, 4);
0801 
0802         if (kInfoProc == nullptr) {
0803             return false;
0804         }
0805 
0806 #if HAVE_OS_DRAGONFLYBSD
0807         setName(QString::fromUtf8(kInfoProc->kp_comm));
0808 #else
0809         setName(QString::fromUtf8(kInfoProc->ki_comm));
0810 #endif
0811 
0812         return true;
0813     }
0814 
0815 private:
0816     bool readProcInfo(int pid) override
0817     {
0818         int managementInfoBase[4];
0819 
0820         managementInfoBase[0] = CTL_KERN;
0821         managementInfoBase[1] = KERN_PROC;
0822         managementInfoBase[2] = KERN_PROC_ARGS;
0823         managementInfoBase[3] = pid;
0824 
0825         auto kInfoProc = getProcInfoStruct(managementInfoBase, 4);
0826 
0827         if (kInfoProc == nullptr) {
0828             return false;
0829         }
0830 
0831 #if HAVE_OS_DRAGONFLYBSD
0832         setName(QString::fromUtf8(kInfoProc->kp_comm));
0833         setPid(kInfoProc->kp_pid);
0834         setParentPid(kInfoProc->kp_ppid);
0835         setForegroundPid(kInfoProc->kp_pgid);
0836         setUserId(kInfoProc->kp_uid);
0837 #else
0838         setName(QString::fromUtf8(kInfoProc->ki_comm));
0839         setPid(kInfoProc->ki_pid);
0840         setParentPid(kInfoProc->ki_ppid);
0841         setForegroundPid(kInfoProc->ki_pgid);
0842         setUserId(kInfoProc->ki_uid);
0843 #endif
0844 
0845         readUserName();
0846 
0847         return true;
0848     }
0849 
0850     bool readArguments(int pid) override
0851     {
0852         int managementInfoBase[4];
0853         char args[ARG_MAX];
0854         size_t len;
0855 
0856         managementInfoBase[0] = CTL_KERN;
0857         managementInfoBase[1] = KERN_PROC;
0858         managementInfoBase[2] = KERN_PROC_ARGS;
0859         managementInfoBase[3] = pid;
0860 
0861         len = sizeof(args);
0862         if (sysctl(managementInfoBase, 4, args, &len, NULL, 0) == -1) {
0863             return false;
0864         }
0865 
0866         // len holds the length of the string
0867         const QStringList argurments = QString::fromLocal8Bit(args, len).split(QLatin1Char('\u0000'));
0868         for (const QString &value : argurments) {
0869             if (!value.isEmpty()) {
0870                 addArgument(value);
0871             }
0872         }
0873 
0874         return true;
0875     }
0876 };
0877 
0878 #elif defined(Q_OS_OPENBSD)
0879 class OpenBSDProcessInfo : public UnixProcessInfo
0880 {
0881 public:
0882     explicit OpenBSDProcessInfo(int pid)
0883         : UnixProcessInfo(pid)
0884     {
0885     }
0886 
0887 protected:
0888     bool readCurrentDir(int pid) override
0889     {
0890         char buf[PATH_MAX];
0891         int managementInfoBase[3];
0892         size_t len;
0893 
0894         managementInfoBase[0] = CTL_KERN;
0895         managementInfoBase[1] = KERN_PROC_CWD;
0896         managementInfoBase[2] = pid;
0897 
0898         len = sizeof(buf);
0899         if (::sysctl(managementInfoBase, 3, buf, &len, NULL, 0) == -1) {
0900             qWarning() << "sysctl() call failed with code" << errno;
0901             return false;
0902         }
0903 
0904         setCurrentDir(QString::fromUtf8(buf));
0905         return true;
0906     }
0907 
0908     bool readProcessName(int pid) override
0909     {
0910         int managementInfoBase[6];
0911 
0912         managementInfoBase[0] = CTL_KERN;
0913         managementInfoBase[1] = KERN_PROC;
0914         managementInfoBase[2] = KERN_PROC_PID;
0915         managementInfoBase[3] = pid;
0916         managementInfoBase[4] = sizeof(struct kinfo_proc);
0917         managementInfoBase[5] = 1;
0918 
0919         auto kInfoProc = getProcInfoStruct(managementInfoBase, 6);
0920 
0921         if (kInfoProc == nullptr) {
0922             return false;
0923         }
0924 
0925         setName(QString::fromUtf8(kInfoProc->p_comm));
0926 
0927         return true;
0928     }
0929 
0930 private:
0931     bool readProcInfo(int pid) override
0932     {
0933         int managementInfoBase[6];
0934 
0935         managementInfoBase[0] = CTL_KERN;
0936         managementInfoBase[1] = KERN_PROC;
0937         managementInfoBase[2] = KERN_PROC_PID;
0938         managementInfoBase[3] = pid;
0939         managementInfoBase[4] = sizeof(struct kinfo_proc);
0940         managementInfoBase[5] = 1;
0941 
0942         auto kInfoProc = getProcInfoStruct(managementInfoBase, 6);
0943 
0944         if (kInfoProc == nullptr) {
0945             return false;
0946         }
0947 
0948         setName(QString::fromUtf8(kInfoProc->p_comm));
0949         setPid(kInfoProc->p_pid);
0950         setParentPid(kInfoProc->p_ppid);
0951         setForegroundPid(kInfoProc->p_tpgid);
0952         setUserId(kInfoProc->p_uid);
0953         setUserName(QString::fromUtf8(kInfoProc->p_login));
0954 
0955         return true;
0956     }
0957 
0958     char **readProcArgs(int pid, int whatMib)
0959     {
0960         void *buf = NULL;
0961         void *nbuf;
0962         size_t len = 4096;
0963         int rc = -1;
0964         int managementInfoBase[4];
0965 
0966         managementInfoBase[0] = CTL_KERN;
0967         managementInfoBase[1] = KERN_PROC_ARGS;
0968         managementInfoBase[2] = pid;
0969         managementInfoBase[3] = whatMib;
0970 
0971         do {
0972             len *= 2;
0973             nbuf = realloc(buf, len);
0974             if (nbuf == NULL) {
0975                 break;
0976             }
0977 
0978             buf = nbuf;
0979             rc = ::sysctl(managementInfoBase, 4, buf, &len, NULL, 0);
0980             qWarning() << "sysctl() call failed with code" << errno;
0981         } while (rc == -1 && errno == ENOMEM);
0982 
0983         if (nbuf == NULL || rc == -1) {
0984             free(buf);
0985             return NULL;
0986         }
0987 
0988         return (char **)buf;
0989     }
0990 
0991     bool readArguments(int pid) override
0992     {
0993         char **argv;
0994 
0995         argv = readProcArgs(pid, KERN_PROC_ARGV);
0996         if (argv == NULL) {
0997             return false;
0998         }
0999 
1000         for (char **p = argv; *p != NULL; p++) {
1001             addArgument(QString::fromUtf8(*p));
1002         }
1003         free(argv);
1004         return true;
1005     }
1006 };
1007 
1008 #elif defined(Q_OS_MACOS)
1009 class MacProcessInfo : public UnixProcessInfo
1010 {
1011 public:
1012     explicit MacProcessInfo(int pid)
1013         : UnixProcessInfo(pid)
1014     {
1015     }
1016 
1017 protected:
1018     bool readCurrentDir(int pid) override
1019     {
1020         struct proc_vnodepathinfo vpi;
1021         const int nb = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
1022         if (nb == sizeof(vpi)) {
1023             setCurrentDir(QString::fromUtf8(vpi.pvi_cdir.vip_path));
1024             return true;
1025         }
1026         return false;
1027     }
1028 
1029     bool readProcessName(int pid) override
1030     {
1031         int managementInfoBase[4];
1032 
1033         managementInfoBase[0] = CTL_KERN;
1034         managementInfoBase[1] = KERN_PROC;
1035         managementInfoBase[2] = KERN_PROC_PID;
1036         managementInfoBase[3] = pid;
1037 
1038         auto kInfoProc = getProcInfoStruct(managementInfoBase, 4);
1039 
1040         if (kInfoProc != nullptr) {
1041             setName(QString::fromUtf8(kInfoProc->kp_proc.p_comm));
1042             return true;
1043         }
1044 
1045         return false;
1046     }
1047 
1048 private:
1049     bool readProcInfo(int pid) override
1050     {
1051         QT_STATBUF statInfo;
1052         int managementInfoBase[4];
1053 
1054         managementInfoBase[0] = CTL_KERN;
1055         managementInfoBase[1] = KERN_PROC;
1056         managementInfoBase[2] = KERN_PROC_PID;
1057         managementInfoBase[3] = pid;
1058 
1059         // Find the tty device of 'pid' (Example: /dev/ttys001)
1060         auto kInfoProc = getProcInfoStruct(managementInfoBase, 4);
1061 
1062         if (kInfoProc == nullptr) {
1063             return false;
1064         }
1065 
1066         const QString deviceNumber = QString::fromUtf8(devname(((&kInfoProc->kp_eproc)->e_tdev), S_IFCHR));
1067         const QString fullDeviceName = QStringLiteral("/dev/") + deviceNumber.rightJustified(3, QLatin1Char('0'));
1068 
1069         setParentPid(kInfoProc->kp_eproc.e_ppid);
1070         setForegroundPid(kInfoProc->kp_eproc.e_pgid);
1071 
1072         const QByteArray deviceName = fullDeviceName.toLatin1();
1073         const char *ttyName = deviceName.data();
1074 
1075         if (QT_STAT(ttyName, &statInfo) != 0) {
1076             return false;
1077         }
1078 
1079         managementInfoBase[2] = KERN_PROC_TTY;
1080         managementInfoBase[3] = statInfo.st_rdev;
1081 
1082         // Find all processes attached to ttyName
1083         kInfoProc = getProcInfoStruct(managementInfoBase, 4);
1084 
1085         if (kInfoProc == nullptr) {
1086             return false;
1087         }
1088 
1089         // The foreground program is the first one
1090         setName(QString::fromUtf8(kInfoProc->kp_proc.p_comm));
1091 
1092         setPid(pid);
1093 
1094         // Get user id - this will allow using username
1095         struct proc_bsdshortinfo bsdinfo;
1096         int ret;
1097         bool ok = false;
1098         const int fpid = foregroundPid(&ok);
1099         if (ok) {
1100             ret = proc_pidinfo(fpid, PROC_PIDT_SHORTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
1101             if (ret == sizeof(bsdinfo)) {
1102                 setUserId(bsdinfo.pbsi_uid);
1103             }
1104             // This will cause constant opening of /etc/passwd
1105             if (userNameRequired()) {
1106                 readUserName();
1107                 setUserNameRequired(false);
1108             }
1109         }
1110 
1111         return true;
1112     }
1113 
1114     bool readArguments(int pid) override
1115     {
1116         int managementInfoBase[3];
1117         size_t size;
1118         std::string procargs;
1119 
1120         managementInfoBase[0] = CTL_KERN;
1121         managementInfoBase[1] = KERN_PROCARGS2;
1122         managementInfoBase[2] = pid;
1123 
1124         // It is not clear on why this fails for some commands
1125         if (sysctl(managementInfoBase, 3, nullptr, &size, nullptr, 0) == -1) {
1126             qWarning() << "OS_MACOS: unable to obtain argument size for " << pid;
1127             return false;
1128         }
1129 
1130         // Some macosx versions need extra space
1131         const size_t argmax = size + 32;
1132         procargs.resize(argmax);
1133 
1134         if (sysctl(managementInfoBase, 3, &procargs[0], &size, nullptr, 0) == -1) {
1135             qWarning() << "OS_MACOS: unable to obtain arguments for " << pid;
1136             return false;
1137         } else {
1138             auto args = QString::fromStdString(procargs);
1139             auto parts = args.split(QLatin1Char('\u0000'), Qt::SkipEmptyParts);
1140             // Do a lot of data checks
1141             if (parts.isEmpty()) {
1142                 return false;
1143             }
1144             /*  0: argc as \u####
1145                 1: full command path
1146                 2: command (no path)
1147                 3: first argument
1148                 4+: next arguments
1149                 argc+: a lot of other data including ENV
1150             */
1151             auto argcs = parts[0]; // first string argc
1152             if (argcs.isEmpty()) {
1153                 return false;
1154             }
1155             auto argc = (int)QChar(argcs[0]).unicode();
1156             if (argc < 1) { // trash
1157                 return false;
1158             } else if (argc == 1) { // just command, no args
1159                 addArgument(parts[1]); // this is the full path + command
1160                 return true;
1161             }
1162 
1163             // Check argc + 2(argc/full cmd) is not larger then parts size
1164             argc = qMin(argc + 2, parts.size());
1165 
1166             /* The output is not obvious for some commands:
1167                For example: 'man 3 sysctl' shows arg=4
1168                0: \u0004; 1: /bin/bash; 2: /bin/sh; 3: /usr/bin/man; 4: 3; 5: sysctl
1169              */
1170             for (int i = 2; i < argc; i++) {
1171                 addArgument(parts[i]);
1172             }
1173             return true;
1174         }
1175     }
1176 };
1177 
1178 #elif defined(Q_OS_SOLARIS)
1179 // The procfs structure definition requires off_t to be
1180 // 32 bits, which only applies if FILE_OFFSET_BITS=32.
1181 // Futz around here to get it to compile regardless,
1182 // although some of the structure sizes might be wrong.
1183 // Fortunately, the structures we actually use don't use
1184 // off_t, and we're safe.
1185 #if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)
1186 #undef _FILE_OFFSET_BITS
1187 #endif
1188 #include <procfs.h>
1189 
1190 class SolarisProcessInfo : public UnixProcessInfo
1191 {
1192 public:
1193     explicit SolarisProcessInfo(int pid)
1194         : UnixProcessInfo(pid)
1195     {
1196     }
1197 
1198 protected:
1199     // FIXME: This will have the same issues as BKO 251351; the Linux
1200     // version uses readlink.
1201     bool readCurrentDir(int pid) override
1202     {
1203         QFileInfo info(QString("/proc/%1/path/cwd").arg(pid));
1204         const bool readable = info.isReadable();
1205 
1206         if (readable && info.isSymLink()) {
1207             setCurrentDir(info.symLinkTarget());
1208             return true;
1209         } else {
1210             if (!readable) {
1211                 setError(PermissionsError);
1212             } else {
1213                 setError(UnknownError);
1214             }
1215 
1216             return false;
1217         }
1218     }
1219 
1220     bool readProcessName(int /*pid*/) override
1221     {
1222         if (execNameFile->isOpen()) {
1223             const QString data(execNameFile->readAll());
1224             setName(execNameFile->readAll().constData());
1225             return true;
1226         }
1227         return false;
1228     }
1229 
1230 private:
1231     bool readProcInfo(int pid) override
1232     {
1233         QFile psinfo(QString("/proc/%1/psinfo").arg(pid));
1234         if (psinfo.open(QIODevice::ReadOnly)) {
1235             struct psinfo info;
1236             if (psinfo.read((char *)&info, sizeof(info)) != sizeof(info)) {
1237                 return false;
1238             }
1239 
1240             setParentPid(info.pr_ppid);
1241             setForegroundPid(info.pr_pgid);
1242             setName(info.pr_fname);
1243             setPid(pid);
1244 
1245             // Bogus, because we're treating the arguments as one single string
1246             info.pr_psargs[PRARGSZ - 1] = 0;
1247             addArgument(info.pr_psargs);
1248         }
1249 
1250         _execNameFile = std::make_unique(new QFile());
1251         _execNameFile->setFileName(QString("/proc/%1/execname").arg(pid));
1252         _execNameFile->open(QIODevice::ReadOnly);
1253 
1254         return true;
1255     }
1256 
1257     std::unique_ptr<QFile> _execNameFile;
1258 };
1259 #endif
1260 
1261 ProcessInfo *ProcessInfo::newInstance(int pid, int sessionPid)
1262 {
1263     ProcessInfo *info;
1264 #if defined(Q_OS_LINUX)
1265     info = new LinuxProcessInfo(pid, sessionPid);
1266 #elif defined(Q_OS_SOLARIS)
1267     info = new SolarisProcessInfo(pid);
1268 #elif defined(Q_OS_MACOS)
1269     info = new MacProcessInfo(pid);
1270 #elif defined(Q_OS_FREEBSD)
1271     info = new FreeBSDProcessInfo(pid);
1272 #elif defined(Q_OS_OPENBSD)
1273     info = new OpenBSDProcessInfo(pid);
1274 #else
1275     info = new NullProcessInfo(pid);
1276 #endif
1277     info->readProcessInfo(pid);
1278     return info;
1279 }