1 | /* |
2 | This file is part of the KDE project |
3 | |
4 | SPDX-FileCopyrightText: 2010 Jacopo De Simoi <wilderkde@gmail.com> |
5 | SPDX-FileCopyrightText: 2014 Lukáš Tinkl <ltinkl@redhat.com> |
6 | SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de> |
7 | SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk> |
8 | |
9 | SPDX-License-Identifier: LGPL-2.0-only |
10 | */ |
11 | |
12 | #include "klistopenfilesjob.h" |
13 | |
14 | #include <QDir> |
15 | #include <QList> |
16 | #include <QProcess> |
17 | #include <QRegularExpression> |
18 | #include <QStandardPaths> |
19 | |
20 | class KListOpenFilesJobPrivate |
21 | { |
22 | public: |
23 | KListOpenFilesJobPrivate(KListOpenFilesJob *Job, const QDir &Path) |
24 | : job(Job) |
25 | , path(Path) |
26 | { |
27 | QObject::connect(sender: &lsofProcess, signal: &QProcess::errorOccurred, context: job, slot: [this](QProcess::ProcessError error) { |
28 | lsofError(processError: error); |
29 | }); |
30 | |
31 | QObject::connect(sender: &lsofProcess, signal: qOverload<int, QProcess::ExitStatus>(&QProcess::finished), context: job, slot: [this](int exitCode, QProcess::ExitStatus exitStatus) { |
32 | lsofFinished(exitCode, exitStatus); |
33 | }); |
34 | } |
35 | |
36 | void start() |
37 | { |
38 | if (!path.exists()) { |
39 | emitResult(error: static_cast<int>(KListOpenFilesJob::Error::DoesNotExist), errorText: QObject::tr(s: "Path %1 doesn't exist" ).arg(a: path.path())); |
40 | return; |
41 | } |
42 | |
43 | const QString lsofExec = QStandardPaths::findExecutable(QStringLiteral("lsof" )); |
44 | if (lsofExec.isEmpty()) { |
45 | const QString envPath = QString::fromLocal8Bit(ba: qgetenv(varName: "PATH" )); |
46 | emitResult(error: static_cast<int>(KListOpenFilesJob::Error::InternalError), errorText: QObject::tr(s: "Could not find lsof executable in PATH:" ).arg(a: envPath)); |
47 | return; |
48 | } |
49 | |
50 | lsofProcess.start(program: lsofExec, arguments: {QStringLiteral("-t" ), QStringLiteral("+d" ), path.path()}); |
51 | } |
52 | |
53 | void lsofError(QProcess::ProcessError processError) |
54 | { |
55 | emitResult(error: static_cast<int>(KListOpenFilesJob::Error::InternalError), errorText: QObject::tr(s: "Failed to execute `lsof'. Error code %1" ).arg(a: processError)); |
56 | } |
57 | |
58 | void lsofFinished(int, QProcess::ExitStatus); |
59 | void emitResult(int error, const QString &errorText); |
60 | |
61 | KListOpenFilesJob *job; |
62 | const QDir path; |
63 | bool hasEmittedResult = false; |
64 | QProcess lsofProcess; |
65 | |
66 | KProcessList::KProcessInfoList processInfoList; |
67 | }; |
68 | |
69 | static KProcessList::KProcessInfo findInfoForPid(qint64 pid) |
70 | { |
71 | #ifdef HAVE_PROCSTAT |
72 | // If HAVE_PROCSTAT is defined, then we're on a BSD, and there is a KProcessList implementation |
73 | // that efficiently lists all processes, but KProcessList::processInfo() is slow because |
74 | // it recalculates the list-of-all-processes on each iteration. |
75 | const auto allProcesses = KProcessList::processInfoList(); |
76 | auto it = std::find_if(allProcesses.cbegin(), allProcesses.cend(), [pid](const KProcessList::KProcessInfo &info) { |
77 | return info.pid() == pid; |
78 | }); |
79 | return it != allProcesses.cend() ? *it : KProcessList::KProcessInfo{}; |
80 | #else |
81 | // Presumably Linux: processInfo(pid) is fine because it goes |
82 | // straight to /proc/<pid> for information. |
83 | return KProcessList::processInfo(pid); |
84 | #endif |
85 | } |
86 | |
87 | void KListOpenFilesJobPrivate::lsofFinished(int, QProcess::ExitStatus) |
88 | { |
89 | if (hasEmittedResult) { |
90 | return; |
91 | } |
92 | const QString out(QString::fromLocal8Bit(ba: lsofProcess.readAll())); |
93 | |
94 | const QRegularExpression re(QStringLiteral("\\s+" )); |
95 | const QList<QStringView> pidList = QStringView(out).split(sep: re, behavior: Qt::SkipEmptyParts); |
96 | |
97 | for (const auto &pidStr : pidList) { |
98 | const qint64 pid = pidStr.toLongLong(); |
99 | if (pid) { |
100 | processInfoList << findInfoForPid(pid); |
101 | } |
102 | } |
103 | job->emitResult(); |
104 | } |
105 | |
106 | void KListOpenFilesJobPrivate::emitResult(int error, const QString &errorText) |
107 | { |
108 | if (hasEmittedResult) { |
109 | return; |
110 | } |
111 | job->setError(error); |
112 | job->setErrorText(errorText); |
113 | job->emitResult(); |
114 | hasEmittedResult = true; |
115 | } |
116 | |
117 | KListOpenFilesJob::KListOpenFilesJob(const QString &path) |
118 | : d(new KListOpenFilesJobPrivate(this, path)) |
119 | { |
120 | } |
121 | |
122 | KListOpenFilesJob::~KListOpenFilesJob() = default; |
123 | |
124 | void KListOpenFilesJob::start() |
125 | { |
126 | d->start(); |
127 | } |
128 | |
129 | KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const |
130 | { |
131 | return d->processInfoList; |
132 | } |
133 | |
134 | #include "moc_klistopenfilesjob.cpp" |
135 | |