File indexing completed on 2024-05-12 03:54:59

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2010 Jacopo De Simoi <wilderkde@gmail.com>
0005     SPDX-FileCopyrightText: 2014 Lukáš Tinkl <ltinkl@redhat.com>
0006     SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
0007     SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-only
0010 */
0011 
0012 #include "klistopenfilesjob.h"
0013 
0014 #include <QDir>
0015 #include <QList>
0016 #include <QProcess>
0017 #include <QRegularExpression>
0018 #include <QStandardPaths>
0019 
0020 class KListOpenFilesJobPrivate
0021 {
0022 public:
0023     KListOpenFilesJobPrivate(KListOpenFilesJob *Job, const QDir &Path)
0024         : job(Job)
0025         , path(Path)
0026     {
0027         QObject::connect(&lsofProcess, &QProcess::errorOccurred, job, [this](QProcess::ProcessError error) {
0028             lsofError(error);
0029         });
0030 
0031         QObject::connect(&lsofProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), job, [this](int exitCode, QProcess::ExitStatus exitStatus) {
0032             lsofFinished(exitCode, exitStatus);
0033         });
0034     }
0035 
0036     void start()
0037     {
0038         if (!path.exists()) {
0039             emitResult(static_cast<int>(KListOpenFilesJob::Error::DoesNotExist), QObject::tr("Path %1 doesn't exist").arg(path.path()));
0040             return;
0041         }
0042 
0043         const QString lsofExec = QStandardPaths::findExecutable(QStringLiteral("lsof"));
0044         if (lsofExec.isEmpty()) {
0045             const QString envPath = QString::fromLocal8Bit(qgetenv("PATH"));
0046             emitResult(static_cast<int>(KListOpenFilesJob::Error::InternalError), QObject::tr("Could not find lsof executable in PATH:").arg(envPath));
0047             return;
0048         }
0049 
0050         lsofProcess.start(lsofExec, {QStringLiteral("-t"), QStringLiteral("+d"), path.path()});
0051     }
0052 
0053     void lsofError(QProcess::ProcessError processError)
0054     {
0055         emitResult(static_cast<int>(KListOpenFilesJob::Error::InternalError), QObject::tr("Failed to execute `lsof'. Error code %1").arg(processError));
0056     }
0057 
0058     void lsofFinished(int, QProcess::ExitStatus);
0059     void emitResult(int error, const QString &errorText);
0060 
0061     KListOpenFilesJob *job;
0062     const QDir path;
0063     bool hasEmittedResult = false;
0064     QProcess lsofProcess;
0065 
0066     KProcessList::KProcessInfoList processInfoList;
0067 };
0068 
0069 static KProcessList::KProcessInfo findInfoForPid(qint64 pid)
0070 {
0071 #ifdef HAVE_PROCSTAT
0072     // If HAVE_PROCSTAT is defined, then we're on a BSD, and there is a KProcessList implementation
0073     // that efficiently lists all processes, but KProcessList::processInfo() is slow because
0074     // it recalculates the list-of-all-processes on each iteration.
0075     const auto allProcesses = KProcessList::processInfoList();
0076     auto it = std::find_if(allProcesses.cbegin(), allProcesses.cend(), [pid](const KProcessList::KProcessInfo &info) {
0077         return info.pid() == pid;
0078     });
0079     return it != allProcesses.cend() ? *it : KProcessList::KProcessInfo{};
0080 #else
0081     // Presumably Linux: processInfo(pid) is fine because it goes
0082     // straight to /proc/<pid> for information.
0083     return KProcessList::processInfo(pid);
0084 #endif
0085 }
0086 
0087 void KListOpenFilesJobPrivate::lsofFinished(int, QProcess::ExitStatus)
0088 {
0089     if (hasEmittedResult) {
0090         return;
0091     }
0092     const QString out(QString::fromLocal8Bit(lsofProcess.readAll()));
0093 
0094     const QRegularExpression re(QStringLiteral("\\s+"));
0095     const QList<QStringView> pidList = QStringView(out).split(re, Qt::SkipEmptyParts);
0096 
0097     for (const auto &pidStr : pidList) {
0098         const qint64 pid = pidStr.toLongLong();
0099         if (pid) {
0100             processInfoList << findInfoForPid(pid);
0101         }
0102     }
0103     job->emitResult();
0104 }
0105 
0106 void KListOpenFilesJobPrivate::emitResult(int error, const QString &errorText)
0107 {
0108     if (hasEmittedResult) {
0109         return;
0110     }
0111     job->setError(error);
0112     job->setErrorText(errorText);
0113     job->emitResult();
0114     hasEmittedResult = true;
0115 }
0116 
0117 KListOpenFilesJob::KListOpenFilesJob(const QString &path)
0118     : d(new KListOpenFilesJobPrivate(this, path))
0119 {
0120 }
0121 
0122 KListOpenFilesJob::~KListOpenFilesJob() = default;
0123 
0124 void KListOpenFilesJob::start()
0125 {
0126     d->start();
0127 }
0128 
0129 KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const
0130 {
0131     return d->processInfoList;
0132 }
0133 
0134 #include "moc_klistopenfilesjob.cpp"