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 }