diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -29,6 +29,20 @@ add_definitions( -DKDELIBS4CONFIGMIGRATOR_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) +if (WIN32) + set(autotests_OPTIONAL_SRCS + ${autotests_OPTIONAL_SRCS} + klistopenfilesjobtest_win.cpp + ) +endif () + +if (UNIX) + set(autotests_OPTIONAL_SRCS + ${autotests_OPTIONAL_SRCS} + klistopenfilesjobtest_unix.cpp + ) +endif () + ecm_add_tests( kaboutdatatest.cpp kaboutdataapplicationdatatest.cpp @@ -51,6 +65,7 @@ kdelibs4configmigratortest.cpp kprocesslisttest.cpp kfileutilstest.cpp + ${autotests_OPTIONAL_SRCS} LINK_LIBRARIES Qt5::Test KF5::CoreAddons ) diff --git a/autotests/klistopenfilesjobtest_unix.h b/autotests/klistopenfilesjobtest_unix.h new file mode 100644 --- /dev/null +++ b/autotests/klistopenfilesjobtest_unix.h @@ -0,0 +1,36 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KLISTOPENFILESJOBTEST_UNIX_H +#define KLISTOPENFILESJOBTEST_UNIX_H + +#include + +class KListOpenFilesJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testOpenFiles(); + void testNoOpenFiles(); + void testNonExistingDir(); + void testLsofNotFound(); +}; + +#endif diff --git a/autotests/klistopenfilesjobtest_unix.cpp b/autotests/klistopenfilesjobtest_unix.cpp new file mode 100644 --- /dev/null +++ b/autotests/klistopenfilesjobtest_unix.cpp @@ -0,0 +1,98 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "klistopenfilesjobtest_unix.h" +#include "klistopenfilesjob.h" +#include +#include +#include +#include +#include + +QTEST_MAIN(KListOpenFilesJobTest) + +void KListOpenFilesJobTest::testOpenFiles() +{ + QDir path(QCoreApplication::applicationDirPath()); + auto job = new KListOpenFilesJob(path.path()); + job->exec(); + QCOMPARE(job->error(), KJob::NoError); + auto processInfoList = job->processInfoList(); + QVERIFY(!processInfoList.empty()); + auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), + [](const KProcessList::KProcessInfo& info) + { + return info.pid() == QCoreApplication::applicationPid(); + }); + QVERIFY(testProcessIterator != processInfoList.end()); + const auto& processInfo = *testProcessIterator; + QVERIFY(processInfo.isValid()); + QCOMPARE(processInfo.pid(), QCoreApplication::applicationPid()); +} + +void KListOpenFilesJobTest::testNoOpenFiles() +{ + QTemporaryDir tempDir; + auto job = new KListOpenFilesJob(tempDir.path()); + job->exec(); + QCOMPARE(job->error(), KJob::NoError); + QVERIFY(job->processInfoList().empty()); +} + +void KListOpenFilesJobTest::testNonExistingDir() +{ + QString nonExistingDir(QStringLiteral("/does/not/exist")); + auto job = new KListOpenFilesJob(nonExistingDir); + job->exec(); + QCOMPARE(job->error(), static_cast(KListOpenFilesJob::Error::DoesNotExist)); + QCOMPARE(job->errorText(), QStringLiteral("Path %1 doesn't exist").arg(nonExistingDir)); + QVERIFY(job->processInfoList().empty()); +} + +/** + * @brief Helper class to temporarily set an environment variable and reset it on destruction + */ +class ScopedEnvVariable +{ +public: + ScopedEnvVariable(const QLatin1String& Name, const QByteArray& NewValue) + : name(Name) + , originalValue(qgetenv(name.latin1())) + { + qputenv(name.latin1(), NewValue); + } + ~ScopedEnvVariable() + { + qputenv(name.latin1(), originalValue); + } +private: + const QLatin1String name; + const QByteArray originalValue; +}; + +void KListOpenFilesJobTest::testLsofNotFound() +{ + // This test relies on clearing the PATH variable so that lsof is not found + ScopedEnvVariable emptyPathEnvironment(QLatin1String("PATH"), QByteArray()); + QDir path(QCoreApplication::applicationDirPath()); + auto job = new KListOpenFilesJob(path.path()); + job->exec(); + QCOMPARE(job->error(), static_cast(KListOpenFilesJob::Error::InternalError)); + QVERIFY(job->processInfoList().empty()); +} diff --git a/autotests/klistopenfilesjobtest_win.h b/autotests/klistopenfilesjobtest_win.h new file mode 100644 --- /dev/null +++ b/autotests/klistopenfilesjobtest_win.h @@ -0,0 +1,33 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KLISTOPENFILESJOBTEST_WIN_H +#define KLISTOPENFILESJOBTEST_WIN_H + +#include + +class KListOpenFilesJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testNotSupported(); +}; + +#endif diff --git a/autotests/klistopenfilesjobtest_win.cpp b/autotests/klistopenfilesjobtest_win.cpp new file mode 100644 --- /dev/null +++ b/autotests/klistopenfilesjobtest_win.cpp @@ -0,0 +1,36 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "klistopenfilesjobtest_win.h" +#include "klistopenfilesjob.h" +#include +#include +#include + +QTEST_MAIN(KListOpenFilesJobTest) + +void KListOpenFilesJobTest::testNotSupported() +{ + QDir path(QCoreApplication::applicationDirPath()); + auto job = new KListOpenFilesJob(path.path()); + job->exec(); + QCOMPARE(job->error(), static_cast(KListOpenFilesJob::Error::NotSupported)); + QCOMPARE(job->errorText(), QStringLiteral("KListOpenFilesJob is not supported on Windows")); + QVERIFY(job->processInfoList().empty()); +} diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -45,6 +45,7 @@ set(kcoreaddons_OPTIONAL_SRCS ${kcoreaddons_OPTIONAL_SRCS} text/kmacroexpander_win.cpp + util/klistopenfilesjob_win.cpp util/kprocesslist_win.cpp util/kshell_win.cpp util/kuser_win.cpp @@ -55,6 +56,7 @@ set(kcoreaddons_OPTIONAL_SRCS ${kcoreaddons_OPTIONAL_SRCS} text/kmacroexpander_unix.cpp + util/klistopenfilesjob_unix.cpp util/kprocesslist_unix.cpp util/kuser_unix.cpp util/kshell_unix.cpp @@ -249,6 +251,7 @@ text/ktexttohtml.h text/ktexttohtmlemoticonsinterface.h util/kformat.h + util/klistopenfilesjob.h util/kosrelease.h util/kprocesslist.h util/kuser.h diff --git a/src/lib/util/klistopenfilesjob.h b/src/lib/util/klistopenfilesjob.h new file mode 100644 --- /dev/null +++ b/src/lib/util/klistopenfilesjob.h @@ -0,0 +1,80 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2010 by Jacopo De Simoi + * Copyright (C) 2014 by Lukáš Tinkl + * Copyright (C) 2016 by Kai Uwe Broulik + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KLISTOPENFILESJOB_H +#define KLISTOPENFILESJOB_H + +#include +#include +#include +#include +#include +#include + +class KListOpenFilesJobPrivate; + + +/** + * @brief Provides information about processes that have open files in a given path or subdirectory of path. + * + * When start() is invoked it starts to collect information about processes that have any files open in path or a + * subdirectory of path. When it is done the KJob::result signal is emitted and the result can be retrieved with the + * processInfoList function. + * + * On Unix like systems the lsof utility is used to get the list of processes. + * On Windows the listing always fails with error code NotSupported. + * + * @since 5.63 + */ +class KCOREADDONS_EXPORT KListOpenFilesJob : public KJob +{ + Q_OBJECT +public: + explicit KListOpenFilesJob(const QString &path); + ~KListOpenFilesJob() override; + void start() override; + /** + * @brief Returns the list of processes with open files for the requested path + * @return The list of processes with open files for the requested path + */ + KProcessList::KProcessInfoList processInfoList() const; + +public: + /** + * @brief Special error codes emitted by KListOpenFilesJob + * + * The KListOpenFilesJob uses the error codes defined here besides the standard error codes defined by KJob + */ + enum class Error { + /*** Indicates that the platform doesn't support listing open files by processes */ + NotSupported = KJob::UserDefinedError + 1, + /*** Internal error has ocurred */ + InternalError = KJob::UserDefinedError + 2, + /*** The specified path does not exist */ + DoesNotExist = KJob::UserDefinedError + 11, + }; +private: + friend class KListOpenFilesJobPrivate; + QScopedPointer d; +}; + +#endif // KLISTOPENFILESJOB_H diff --git a/src/lib/util/klistopenfilesjob_unix.cpp b/src/lib/util/klistopenfilesjob_unix.cpp new file mode 100644 --- /dev/null +++ b/src/lib/util/klistopenfilesjob_unix.cpp @@ -0,0 +1,115 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2010 by Jacopo De Simoi + * Copyright (C) 2014 by Lukáš Tinkl + * Copyright (C) 2016 by Kai Uwe Broulik + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "klistopenfilesjob.h" +#include +#include + +class KListOpenFilesJobPrivate +{ +public: + KListOpenFilesJobPrivate(KListOpenFilesJob *Job, QDir Path) + : job(Job) + , path(Path) + , hasEmittedResult(false) + { + QObject::connect(&lsofProcess, &QProcess::errorOccurred, [this](QProcess::ProcessError error) { + lsofError(error); + }); + QObject::connect(&lsofProcess, QOverload::of(&QProcess::finished), + [this](int exitCode, QProcess::ExitStatus exitStatus) { + lsofFinished(exitCode, exitStatus); + }); + } + void start() + { + if (!path.exists()) { + emitResult(static_cast(KListOpenFilesJob::Error::DoesNotExist), + QObject::tr("Path %1 doesn't exist").arg(path.path())); + return; + } + lsofProcess.start(QStringLiteral("lsof"), {QStringLiteral("-t"), QStringLiteral("+d"), path.path()}); + } + KProcessList::KProcessInfoList getProcessInfoList() const + { + return processInfoList; + } +private: + void lsofError(QProcess::ProcessError processError) + { + emitResult(static_cast(KListOpenFilesJob::Error::InternalError), + QObject::tr("Failed to execute `lsof' error code %1").arg(processError)); + } + void lsofFinished(int, QProcess::ExitStatus) + { + if (hasEmittedResult) { + return; + } + QStringList blockApps; + const QString out(QString::fromLocal8Bit(lsofProcess.readAll())); + QStringList pidList = out.split(QRegExp(QStringLiteral("\\s+")), QString::SkipEmptyParts); + pidList.removeDuplicates(); + for (const auto& pidStr : qAsConst(pidList)) { + qint64 pid = pidStr.toLongLong(); + if (!pid) { + continue; + } + processInfoList << KProcessList::processInfo(pid); + } + job->emitResult(); + } + void emitResult(int error, const QString& errorText) + { + if (hasEmittedResult) { + return; + } + job->setError(error); + job->setErrorText(errorText); + job->emitResult(); + hasEmittedResult = true; + } +private: + KListOpenFilesJob *job; + const QDir path; + bool hasEmittedResult; + QProcess lsofProcess; + KProcessList::KProcessInfoList processInfoList; +}; + +KListOpenFilesJob::KListOpenFilesJob(const QString& path) + : d(new KListOpenFilesJobPrivate(this, path)) +{ +} + +KListOpenFilesJob::~KListOpenFilesJob() = default; + +void KListOpenFilesJob::start() +{ + d->start(); +} + +KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const +{ + return d->getProcessInfoList(); +} + +#include "moc_klistopenfilesjob.cpp" diff --git a/src/lib/util/klistopenfilesjob_win.cpp b/src/lib/util/klistopenfilesjob_win.cpp new file mode 100644 --- /dev/null +++ b/src/lib/util/klistopenfilesjob_win.cpp @@ -0,0 +1,48 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 David Hallas + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "klistopenfilesjob.h" +#include + +class KListOpenFilesJobPrivate +{ +}; + +KListOpenFilesJob::KListOpenFilesJob(const QString&) + : d(nullptr) +{ +} + +KListOpenFilesJob::~KListOpenFilesJob() = default; + +void KListOpenFilesJob::start() +{ + QTimer::singleShot(0, [this](){ + setError(static_cast(KListOpenFilesJob::Error::NotSupported)); + setErrorText(QObject::tr("KListOpenFilesJob is not supported on Windows")); + emitResult(); + }); +} + +KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const +{ + return KProcessList::KProcessInfoList(); +} + +#include "moc_klistopenfilesjob.cpp"