diff --git a/.reuse/dep5 b/.reuse/dep5 index cbfe56a..9d71481 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -4,7 +4,7 @@ Upstream-Contact: UnionTech Software Technology Co., Ltd. <> Source: https://github.com/linuxdeepin/dde-application-manager # README & DOC -Files: README.md README.zh_CN.md docs/* +Files: README.md README.zh_CN.md docs/* examples/launchApp/README.md Copyright: UnionTech Software Technology Co., Ltd. License: CC-BY-4.0 diff --git a/apps/app-launch-helper/src/main.cpp b/apps/app-launch-helper/src/main.cpp index 5f89200..93fa592 100644 --- a/apps/app-launch-helper/src/main.cpp +++ b/apps/app-launch-helper/src/main.cpp @@ -17,6 +17,8 @@ #include #include "constant.h" +namespace { + enum class ExitCode { SystemdError = -3, InvalidInput = -2, InternalError = -1, Done = 0, Waiting = 1 }; struct JobRemoveResult @@ -29,7 +31,7 @@ struct JobRemoveResult using msg_ptr = sd_bus_message *; using bus_ptr = sd_bus *; -static ExitCode fromString(const std::string &str) +ExitCode fromString(const std::string &str) { if (str == "done") { return ExitCode::Done; @@ -50,7 +52,7 @@ static ExitCode fromString(const std::string &str) __builtin_unreachable(); } -static ExitCode fromString(const char *str) +ExitCode fromString(const char *str) { if (!str) { return ExitCode::Waiting; @@ -59,7 +61,7 @@ static ExitCode fromString(const char *str) return fromString(tmp); } -[[noreturn]] static void releaseRes(sd_bus_error &error, msg_ptr &msg, bus_ptr &bus, ExitCode ret) +[[noreturn]] void releaseRes(sd_bus_error &error, msg_ptr &msg, bus_ptr &bus, ExitCode ret) { sd_bus_error_free(&error); sd_bus_message_unref(msg); @@ -68,7 +70,7 @@ static ExitCode fromString(const char *str) std::exit(static_cast(ret)); } -static int processExecStart(msg_ptr &msg, const std::deque &execArgs) +int processExecStart(msg_ptr &msg, const std::deque &execArgs) { int ret; if (ret = sd_bus_message_append(msg, "s", "ExecStart"); ret < 0) { @@ -139,7 +141,7 @@ static int processExecStart(msg_ptr &msg, const std::deque &ex return 0; } -static int processKVPair(msg_ptr &msg, const std::map &props) +int processKVPair(msg_ptr &msg, const std::map &props) { int ret; if (!props.empty()) { @@ -155,7 +157,7 @@ static int processKVPair(msg_ptr &msg, const std::map &&cmdLines) +std::string cmdParse(msg_ptr &msg, std::deque &&cmdLines) { std::string serviceName{"internalError"}; std::map props; @@ -241,6 +243,11 @@ static std::string cmdParse(msg_ptr &msg, std::deque &&cmdLine return serviceName; } + if (ret = sd_bus_message_append(msg, "(sv)", "Slice", "s", "app.slice"); ret < 0) { + sd_journal_perror("append application slice failed."); + return serviceName; + } + if (ret = sd_bus_message_open_container(msg, SD_BUS_TYPE_STRUCT, "sv"); ret < 0) { sd_journal_perror("open struct failed."); return serviceName; @@ -300,7 +307,7 @@ int jobRemovedReceiver(sd_bus_message *m, void *userdata, sd_bus_error *ret_erro return ret; } -static int process_dbus_message(sd_bus *bus) +int process_dbus_message(sd_bus *bus) { int ret; ret = sd_bus_process(bus, nullptr); @@ -322,6 +329,8 @@ static int process_dbus_message(sd_bus *bus) return ret; } +} // namespace + int main(int argc, const char *argv[]) { sd_bus_error error{SD_BUS_ERROR_NULL}; diff --git a/apps/dde-application-manager/src/main.cpp b/apps/dde-application-manager/src/main.cpp index 226545a..d25aeba 100644 --- a/apps/dde-application-manager/src/main.cpp +++ b/apps/dde-application-manager/src/main.cpp @@ -9,12 +9,14 @@ #include "dbus/applicationmanager1service.h" #include "cgroupsidentifier.h" -static void registerComplexDbusType() +namespace { +void registerComplexDbusType() { qDBusRegisterMetaType>(); qDBusRegisterMetaType>>(); qDBusRegisterMetaType(); } +} // namespace int main(int argc, char *argv[]) { @@ -22,7 +24,7 @@ int main(int argc, char *argv[]) auto &bus = ApplicationManager1DBus::instance(); bus.initGlobalServerBus(DBusType::Session); - bus.setDestBus(""); + bus.setDestBus(); auto &AMBus = bus.globalServerBus(); registerComplexDbusType(); diff --git a/debian/control b/debian/control index bb62e1a..80fd203 100644 --- a/debian/control +++ b/debian/control @@ -3,6 +3,11 @@ Section: devel Priority: optional Maintainer: Chen Linxuan Build-Depends: + cmake, + libsystemd-dev, + qt6-base-dev, + libgtest-dev, + pkg-config Standards-Version: 4.1.3 Homepage: https://github.com/linuxdeepin/dde-application-manager Package: dde-application-manager-reborn diff --git a/src/cgroupsidentifier.cpp b/src/cgroupsidentifier.cpp index d6372ac..dfb3d4f 100644 --- a/src/cgroupsidentifier.cpp +++ b/src/cgroupsidentifier.cpp @@ -16,14 +16,13 @@ IdentifyRet CGroupsIdentifier::Identify(pid_t pid) qWarning() << "open " << AppCgroupPath << "failed: " << AppCgroupFile.errorString(); return {}; } - auto appInstance = parseCGroupsPath( - AppCgroupFile.readAll().split(':').last().trimmed()); // FIXME: support CGroup version detection and multi-line parsing - auto appInstanceComponent = appInstance.split('@'); - if (appInstanceComponent.count() != 2) { - qWarning() << "Application Instance format is invalid." << appInstanceComponent; - return {}; - } - return {appInstanceComponent.first(), appInstanceComponent.last()}; + auto UnitStr = parseCGroupsPath(QString::fromLocal8Bit(AppCgroupFile.readAll()) + .split(':', Qt::SkipEmptyParts) + .last() + .trimmed()); // FIXME: support CGroup version detection and multi-line parsing + + auto [appId, InstanceId] = processUnitName(UnitStr); + return {std::move(appId), std::move(InstanceId)}; } QString CGroupsIdentifier::parseCGroupsPath(const QString &CGP) noexcept @@ -32,8 +31,7 @@ QString CGroupsIdentifier::parseCGroupsPath(const QString &CGP) noexcept qWarning() << "CGroupPath is empty."; return {}; } - auto unescapedCGP = unescapeString(CGP); - auto CGPSlices = unescapedCGP.split('/'); + auto CGPSlices = CGP.split('/', Qt::SkipEmptyParts); if (CGPSlices.first() != "user.slice") { qWarning() << "unrecognized process."; @@ -51,7 +49,7 @@ QString CGroupsIdentifier::parseCGroupsPath(const QString &CGP) noexcept return {}; } - auto appInstance = CGPSlices.last().split('.').first(); + auto appInstance = CGPSlices.last(); if (appInstance.isEmpty()) { qWarning() << "get AppId failed."; return {}; diff --git a/src/dbus/applicationmanager1service.cpp b/src/dbus/applicationmanager1service.cpp index 6ae33c8..dc28b02 100644 --- a/src/dbus/applicationmanager1service.cpp +++ b/src/dbus/applicationmanager1service.cpp @@ -90,41 +90,6 @@ ApplicationManager1Service::ApplicationManager1Service(std::unique_ptr ApplicationManager1Service::processUnitName(const QString &unitName) noexcept -{ - QString instanceId; - QString applicationId; - - if (unitName.endsWith(".service")) { - auto lastDotIndex = unitName.lastIndexOf('.'); - auto app = unitName.sliced(0, lastDotIndex - 1); // remove suffix - - if (app.contains('@')) { - auto atIndex = app.indexOf('@'); - instanceId = app.sliced(atIndex + 1); - app.remove(atIndex, instanceId.length() + 1); - } - - applicationId = app.split('-').last(); // drop launcher if it exists. - } else if (unitName.endsWith(".scope")) { - auto lastDotIndex = unitName.lastIndexOf('.'); - auto app = unitName.sliced(0, lastDotIndex - 1); - - auto components = app.split('-'); - instanceId = components.takeLast(); - applicationId = components.takeLast(); - } else { - qDebug() << "it's not service or scope:" << unitName << "ignore."; - return {}; - } - - if (instanceId.isEmpty()) { - instanceId = QUuid::createUuid().toString(QUuid::Id128); - } - - return qMakePair(unescapeApplicationId(applicationId), std::move(instanceId)); -} - QList ApplicationManager1Service::list() const { return m_applicationList.keys(); diff --git a/src/dbus/applicationmanager1service.h b/src/dbus/applicationmanager1service.h index 929a344..52bdbc2 100644 --- a/src/dbus/applicationmanager1service.h +++ b/src/dbus/applicationmanager1service.h @@ -62,8 +62,6 @@ private: std::unique_ptr m_identifier; QScopedPointer m_jobManager{nullptr}; QMap> m_applicationList; - - static QPair processUnitName(const QString &serviceName) noexcept; }; #endif diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h index 4cd8bf0..de98e5f 100644 --- a/src/dbus/applicationservice.h +++ b/src/dbus/applicationservice.h @@ -64,7 +64,7 @@ public Q_SLOTS: private: friend class ApplicationManager1Service; template - ApplicationService(T &&source, ApplicationManager1Service *parent) + explicit ApplicationService(T &&source, ApplicationManager1Service *parent = nullptr) : m_parent(parent) , m_desktopSource(std::forward(source)) { @@ -84,7 +84,9 @@ private: m_isPersistence = true; sourceFile.setFileName(m_desktopSource.m_file.filePath()); if (!sourceFile.open(QFile::ExistingOnly | QFile::ReadOnly | QFile::Text)) { +#ifndef DEBUG_MODE qCritical() << "desktop file can't open:" << m_desktopSource.m_file.filePath() << sourceFile.errorString(); +#endif return; } sourceStream.setDevice(&sourceFile); diff --git a/src/global.h b/src/global.h index a488cc6..49b65a9 100644 --- a/src/global.h +++ b/src/global.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "constant.h" #include "config.h" @@ -143,7 +144,7 @@ public: return m_destConnection.value(); } - void setDestBus(const QString &destAddress) + void setDestBus(const QString &destAddress = "") { if (m_destConnection) { m_destConnection->disconnectFromBus(ApplicationManagerDestDBusName); @@ -206,26 +207,6 @@ inline QLocale getUserLocale() return QLocale::system(); // current use env } -inline QString unescapeString(const QString &input) -{ - QRegularExpression regex("\\\\x([0-9A-Fa-f]{2})"); - QRegularExpressionMatchIterator it = regex.globalMatch(input); - QString output{input}; - - while (it.hasNext()) { - QRegularExpressionMatch match = it.next(); - bool ok; - // Get the hexadecimal value from the match and convert it to a number. - int asciiValue = match.captured(1).toInt(&ok, 16); - if (ok) { - // Convert the ASCII value to a QChar and perform the replacement. - output.replace(match.capturedStart(), match.capturedLength(), QChar(asciiValue)); - } - } - - return output; -} - inline QString escapeToObjectPath(const QString &str) { if (str.isEmpty()) { @@ -333,4 +314,39 @@ inline QStringList getXDGDataDirs() return XDGDataDirs; } +inline QPair processUnitName(const QString &unitName) +{ + QString instanceId; + QString applicationId; + + if (unitName.endsWith(".service")) { + auto lastDotIndex = unitName.lastIndexOf('.'); + auto app = unitName.sliced(0, lastDotIndex); // remove suffix + + if (app.contains('@')) { + auto atIndex = app.indexOf('@'); + instanceId = app.sliced(atIndex + 1); + app.remove(atIndex, instanceId.length() + 1); + } + + applicationId = app.split('-').last(); // drop launcher if it exists. + } else if (unitName.endsWith(".scope")) { + auto lastDotIndex = unitName.lastIndexOf('.'); + auto app = unitName.sliced(0, lastDotIndex); + + auto components = app.split('-'); + instanceId = components.takeLast(); + applicationId = components.takeLast(); + } else { + qDebug() << "it's not service or scope:" << unitName << "ignore."; + return {}; + } + + if (instanceId.isEmpty()) { + instanceId = QUuid::createUuid().toString(QUuid::Id128); + } + + return qMakePair(unescapeApplicationId(applicationId), std::move(instanceId)); +} + #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 356dce1..fa386b5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,7 +18,16 @@ target_link_libraries(${BIN_NAME} PRIVATE dde_am_static ) -target_compile_options(${BIN_NAME} PRIVATE "-fno-access-control") +target_compile_options(${BIN_NAME} PRIVATE + -fno-access-control + -fsanitize=undefined + -fsanitize=address +) + +target_link_options(${BIN_NAME} PRIVATE + -fsanitize=undefined + -fsanitize=address +) add_test( NAME UnitTest diff --git a/tests/ut_applicationmanager.cpp b/tests/ut_applicationmanager.cpp new file mode 100644 index 0000000..d44caf8 --- /dev/null +++ b/tests/ut_applicationmanager.cpp @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "dbus/applicationmanager1service.h" +#include "dbus/applicationservice.h" +#include "cgroupsidentifier.h" +#include "constant.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +int pidfd_open(pid_t pid, uint flags) +{ + return syscall(SYS_pidfd_open, pid, flags); +} +} // namespace + +class TestApplicationManager : public testing::Test +{ +public: + static void SetUpTestCase() + { + auto &bus = ApplicationManager1DBus::instance(); + bus.initGlobalServerBus(DBusType::Session); + bus.setDestBus(); + + m_am = new ApplicationManager1Service{std::make_unique(), bus.globalServerBus()}; + + DesktopFile file{"/usr/share/applications/test-Application.desktop", "test-Application", 0}; + QSharedPointer app = QSharedPointer::create(std::move(file)); + QSharedPointer instance = + QSharedPointer::create(InstancePath.path().split('/').last(), ApplicationPath.path(), QString{"/"}); + app->m_Instances.insert(InstancePath, instance); + m_am->m_applicationList.insert(ApplicationPath, app); + } + + static void TearDownTestCase() { m_am->deleteLater(); } + + static inline ApplicationManager1Service *m_am{nullptr}; + const static inline QDBusObjectPath ApplicationPath{DDEApplicationManager1ApplicationObjectPath + + QUuid::createUuid().toString(QUuid::Id128)}; + const static inline QDBusObjectPath InstancePath{DDEApplicationManager1InstanceObjectPath + + QUuid::createUuid().toString(QUuid::Id128)}; +}; + +TEST_F(TestApplicationManager, list) +{ + auto lists = m_am->list(); + EXPECT_EQ(lists.first(), ApplicationPath); +} + +TEST_F(TestApplicationManager, application) +{ + EXPECT_EQ(m_am->Application("test-Application"), ApplicationPath); +} + +TEST_F(TestApplicationManager, identifyService) +{ + using namespace std::chrono_literals; + // for service unit + auto workingDir = QDir::cleanPath(QDir::current().absolutePath() + QDir::separator() + ".." + QDir::separator() + "tools"); + QFile pidFile{workingDir + QDir::separator() + "pid.txt"}; + if (pidFile.exists()) { + ASSERT_TRUE(pidFile.remove()); + } + + QProcess fakeServiceProc; + fakeServiceProc.setWorkingDirectory(workingDir); + auto InstanceId = InstancePath.path().split('/').last(); + fakeServiceProc.start("/usr/bin/systemd-run", + {{QString{R"(--unit=app-DDE-test\x2dApplication@%1.service)"}.arg(InstanceId)}, + {"--user"}, + {QString{"--working-directory=%1"}.arg(workingDir)}, + {"--slice=app.slice"}, + {"./fake-process.sh"}}); + fakeServiceProc.waitForFinished(); + if (fakeServiceProc.exitStatus() != QProcess::ExitStatus::NormalExit) { + GTEST_SKIP() << "invoke systemd-run failed."; + } + + std::this_thread::sleep_for(100ms); + + auto success = pidFile.open(QFile::ReadOnly | QFile::Text | QFile::ExistingOnly); + EXPECT_TRUE(success); + + if (!success) { + qWarning() << pidFile.errorString(); + fakeServiceProc.terminate(); + ASSERT_TRUE(false); + } + + bool ok{false}; + auto fakePid = pidFile.readLine().toInt(&ok); + ASSERT_TRUE(ok); + + auto pidfd = pidfd_open(fakePid, 0); + ASSERT_TRUE(pidfd > 0) << std::strerror(errno); + QDBusObjectPath application; + QDBusObjectPath application_instance; + auto appId = m_am->Identify(QDBusUnixFileDescriptor{pidfd}, application, application_instance); + EXPECT_EQ(appId.toStdString(), QString{"test-Application"}.toStdString()); + EXPECT_EQ(application.path().toStdString(), ApplicationPath.path().toStdString()); + EXPECT_EQ(application_instance.path().toStdString(), InstancePath.path().toStdString()); + close(pidfd); + + if (pidFile.exists()) { + pidFile.remove(); + } + + // for scope unit + ASSERT_TRUE(QProcess::startDetached("/usr/bin/systemd-run", + {{"--scope"}, + {QString{R"(--unit=app-DDE-test\x2dApplication-%1.scope)"}.arg(InstanceId)}, + {"--user"}, + {QString{"--working-directory=%1"}.arg(workingDir)}, + {"--slice=app.slice"}, + {"./fake-process.sh"}, + {"Scope"}}, + workingDir)); + + std::this_thread::sleep_for(100ms); + + success = pidFile.open(QFile::ReadOnly | QFile::Text | QFile::ExistingOnly); + EXPECT_TRUE(success); + + if (!success) { + qWarning() << pidFile.errorString(); + ASSERT_TRUE(false); + } + + ok = false; + fakePid = pidFile.readLine().toInt(&ok); + ASSERT_TRUE(ok); + + pidfd = pidfd_open(fakePid, 0); + ASSERT_TRUE(pidfd > 0) << std::strerror(errno); + + appId = m_am->Identify(QDBusUnixFileDescriptor{pidfd}, application, application_instance); + EXPECT_EQ(appId.toStdString(), QString{"test-Application"}.toStdString()); + EXPECT_EQ(application.path().toStdString(), ApplicationPath.path().toStdString()); + EXPECT_EQ(application_instance.path().toStdString(), InstancePath.path().toStdString()); + close(pidfd); + + if (pidFile.exists()) { + pidFile.remove(); + } +} diff --git a/tests/ut_desktopentry.cpp b/tests/ut_desktopentry.cpp index f3b1d04..847e38a 100644 --- a/tests/ut_desktopentry.cpp +++ b/tests/ut_desktopentry.cpp @@ -30,8 +30,6 @@ public: static void TearDownTestCase() { qputenv("XDG_DATA_DIRS", env); } - void SetUp() override {} - void TearDown() override {} QSharedPointer file() { return m_file; } private: diff --git a/tests/ut_jobmanager.cpp b/tests/ut_jobmanager.cpp index 7f09619..c0300f6 100644 --- a/tests/ut_jobmanager.cpp +++ b/tests/ut_jobmanager.cpp @@ -43,5 +43,5 @@ TEST_F(TestJobManager, addJob) return QVariant::fromValue(true); }, args); - QThread::sleep(1); // force wait + QThreadPool::globalInstance()->waitForDone(); } diff --git a/tools/fake-process.sh b/tools/fake-process.sh new file mode 100755 index 0000000..11d92f1 --- /dev/null +++ b/tools/fake-process.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +echo $$ > pid.txt +sleep 1s +exit 0 diff --git a/tools/test-coverage.sh b/tools/test-coverage.sh index df7b6be..1db2636 100755 --- a/tools/test-coverage.sh +++ b/tools/test-coverage.sh @@ -1,6 +1,6 @@ #!/bin/bash -# SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: LGPL-3.0-or-later