Skip to content

Conversation

@ut003640
Copy link
Contributor

@ut003640 ut003640 commented Jan 29, 2026

add the debug for service-manager-plugin-network

Log: add debug for service-manager

Summary by Sourcery

Introduce a simplified Qt6-based example application that can either display a basic DCC plugin test widget or run a service-manager-style DBus service with policy-based access control, replacing the previous dcc-network plugin example and wiring it into the build.

New Features:

  • Allow the example binary to run either a simple DCC plugin test UI or start a DBus-based network service manager using JSON-defined policies.

Enhancements:

  • Replace the complex DccPluginTestWidget and MainModule-based plugin UI with a minimal standalone QWidget window.
  • Refactor the example build to target Qt6, Dtk6, and KF6 NetworkManagerQt, removing legacy dde-control-center and plugin sources and wiring in new service components.
  • Add a reusable DBus service framework (base class, Qt DBus implementation, DBus hook, and policy parser) to support policy-controlled, on-demand service registration in the example app.

Build:

  • Enable building the 'example' subdirectory again and update its CMakeLists to use Qt6, Dtk6, and KF6 NetworkManagerQt, define service config/library paths, and compile the new service sources.

add the debug for service-manager-plugin-network

Log: add debug for service-manager
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 29, 2026

Reviewer's Guide

Refactors the example test application away from the legacy DCC network plugin UI into a minimal Qt6/Dtk6-based widget and new DBus service test harness, wiring in a policy-driven service manager implementation and enabling the example build under Qt6.

Sequence diagram for DBus call filtering via QTDbusHook and Policy

sequenceDiagram
    participant ClientApp
    participant DBusDaemon
    participant QtDBus as Qt_DBUS
    participant QTDbusHook as QTDbusHook_singleton
    participant Service as ServiceQtDBus
    participant Policy

    ClientApp->>DBusDaemon: DBus method call (service, path, interface, member)
    DBusDaemon->>QtDBus: Deliver message to service process
    QtDBus->>QTDbusHook: QTDBusSpyHook(msg) or QTDBusHook(baseService, msg)

    QTDbusHook->>QTDbusHook: getServiceObject("", msg.path, servicePtr, isSubPath, realPath)
    alt service object found
        QTDbusHook->>Service: isRegister()
        alt not registered
            QTDbusHook->>Service: registerService()
        end

        alt msg is Introspect on org.freedesktop.DBus.Introspectable
            QTDbusHook->>Policy: checkPathHide(realPath)
            alt path hidden
                QTDbusHook->>Service: qDbusConnection()
                Service-->>QtDBus: send empty introspection reply
                QtDBus-->>DBusDaemon: send reply
                DBusDaemon-->>ClientApp: receive reply
            else path visible
                QTDbusHook-->>QtDBus: allow normal handling
            end
        else msg is Set on org.freedesktop.DBus.Properties
            QTDbusHook->>Service: qDbusConnection()
            QTDbusHook->>Policy: checkPropertyPermission(cmd, realPath, iface, property)
            alt permission denied
                QTDbusHook-->>QtDBus: send error reply Permission.Deny
                QtDBus-->>DBusDaemon: send error
                DBusDaemon-->>ClientApp: receive error
            else permission granted
                QTDbusHook-->>QtDBus: allow normal handling
            end
        else other interface and member
            QTDbusHook->>Policy: checkMethodPermission(cmd, realPath, interface, member)
            alt permission denied
                QTDbusHook->>Service: qDbusConnection()
                QTDbusHook-->>QtDBus: send error reply Permission.Deny
                QtDBus-->>DBusDaemon: send error
                DBusDaemon-->>ClientApp: receive error
            else permission granted
                QTDbusHook-->>QtDBus: allow normal handling
            end
        end
    else service object not found
        QTDbusHook-->>QtDBus: allow normal handling
    end
Loading

Class diagram for new service manager and policy types

classDiagram
    class Policy {
        +QString name
        +QString group
        +QString pluginPath
        +QString version
        +QString startType
        +QStringList dependencies
        +SDKType sdkType
        +int startDelay
        +int idleTime
        +QMapWhitelists mapWhitelist
        +QMapPathHide mapPathHide
        +QMapSubPath mapSubPath
        +QMapPath mapPath
        +Policy(QObject *parent)
        +void parseConfig(QString path)
        +bool checkPathHide(QString path)
        +bool checkMethodPermission(QString process, QString path, QString interface, QString method)
        +bool checkPropertyPermission(QString process, QString path, QString interface, QString property)
        +bool checkPermission(QString process, QString path, QString interface, QString dest, CallDestType type)
        +QStringList paths()
        +bool allowSubPath(QString path)
        +bool isResident()
        +void print()
        -bool readJsonFile(QJsonDocument outDoc, QString fileName)
        -bool parseWhitelist(QJsonObject obj)
        -bool parsePolicy(QJsonObject obj)
        -bool parsePolicyPath(QJsonObject obj)
        -bool parsePolicyInterface(QJsonObject obj, PolicyPath policyPath)
        -bool parsePolicyMethod(QJsonObject obj, PolicyInterface policyInterface)
        -bool parsePolicyProperties(QJsonObject obj, PolicyInterface policyInterface)
        -bool jsonGetString(QJsonObject obj, QString key, QString value, QString defaultValue)
        -bool jsonGetStringList(QJsonObject obj, QString key, QStringList value, QStringList defaultValue)
        -bool jsonGetBool(QJsonObject obj, QString key, bool value, bool defaultValue)
        -bool jsonGetInt(QJsonObject obj, QString key, int value, int defaultValue)
    }

    class PolicyWhitelist {
        +QString name
        +QStringList process
    }

    class PolicyMethod {
        +QString method
        +bool needPermission
        +QStringList processes
    }

    class PolicyProperty {
        +QString property
        +bool needPermission
        +QStringList processes
    }

    class PolicyInterface {
        +QString interface
        +bool needPermission
        +QStringList processes
        +QMapMethod methods
        +QMapProperty properties
    }

    class PolicyPath {
        +QString path
        +bool needPermission
        +QStringList processes
        +QMapInterface interfaces
    }

    class SDKType {
        <<enum>>
        QT
        SD
    }

    class CallDestType {
        <<enum>>
        Method
        Property
    }

    class ServiceBase {
        +Policy *policy
        +bool m_isRegister
        +bool m_isLockTimer
        +QDBusConnection.BusType m_sessionType
        +SDKType m_SDKType
        +QTimer *m_timer
        +ServiceBase(QObject *parent)
        +~ServiceBase()
        +bool isRegister() const
        +bool isLockTimer() const
        +bool registerService()
        +bool unregisterService()
        +void init(QDBusConnection.BusType busType, Policy *p)
        +void restartTimer()
        +void idleSignal()
        -void initService()
        -void initThread()
    }

    class ServiceQtDBus {
        +QLibrary *m_library
        +ServiceQtDBus(QObject *parent)
        +QDBusConnection qDbusConnection()
        +bool registerService()
        +bool unregisterService()
        -void initThread()
        -bool libFuncCall(QString funcName, bool isRegister)
    }

    class QTDbusHook {
        -ServiceObjectMap m_serviceMap
        +QTDbusHook()
        +bool getServiceObject(QString name, QString path, ServiceBase **service, bool isSubPath, QString realPath)
        +bool setServiceObject(ServiceBase *obj)
        +static QTDbusHook *instance()
    }

    class ServiceObjectMap {
        <<typedef>>
        QMap~QString, ServiceBase *~
    }

    class QMapWhitelists {
        <<typedef>>
        QMap~QString, PolicyWhitelist~
    }

    class QMapPathHide {
        <<typedef>>
        QMap~QString, bool~
    }

    class QMapSubPath {
        <<typedef>>
        QMap~QString, bool~
    }

    class QMapMethod {
        <<typedef>>
        QMap~QString, PolicyMethod~
    }

    class QMapProperty {
        <<typedef>>
        QMap~QString, PolicyProperty~
    }

    class QMapInterface {
        <<typedef>>
        QMap~QString, PolicyInterface~
    }

    class QMapPath {
        <<typedef>>
        QMap~QString, PolicyPath~
    }

    ServiceQtDBus --|> ServiceBase
    ServiceBase o--> Policy
    Policy --> QMapWhitelists
    Policy --> QMapPathHide
    Policy --> QMapSubPath
    Policy --> QMapPath
    PolicyPath --> QMapInterface
    PolicyInterface --> QMapMethod
    PolicyInterface --> QMapProperty
    QTDbusHook --> ServiceObjectMap
    ServiceBase --> SDKType
    Policy --> SDKType
    Policy --> CallDestType
Loading

File-Level Changes

Change Details Files
Simplified DccPluginTestWidget from a DMainWindow-based, module-driven network plugin shell to a minimal QWidget demo with custom painting.
  • Remove MainModule implementation, module navigation logic, and all DCC-related dependencies from the example widget implementation and header
  • Change DccPluginTestWidget base class from DMainWindow to QWidget and strip navigation, page-management, and configuration methods
  • Introduce a simple QVBoxLayout with a centered label, set basic window properties, and add a white-background paintEvent override
  • Update SPDX copyright years to 2018–2026 in the widget source and header
example/dccplugintestwidget.cpp
example/dccplugintestwidget.h
Migrated the example project to Qt6/Dtk6 and re-scoped its build to local sources plus new service testing support.
  • Switch CMake configuration from Qt5/DtkWidget/KF5 dependencies to Qt6, Dtk6::Widget, and KF6::NetworkManagerQt
  • Remove inclusion and compilation of the production dcc-network-plugin sources and proto generation from the example target
  • Define SERVICE_CONFIG_DIR and SERVICE_LIB_DIR macros depending on build type to locate service manager configuration and plugins
  • Change source and resource collection to use example-local .cpp/.h/.qrc files including the new service subdirectory
  • Update include directories and link libraries to point at Qt6, Dtk6, KF6, and dde-network-core6
example/CMakeLists.txt
CMakeLists.txt
Reworked example main program into a launcher that can start either the plugin test UI or a DBus service manager instance with logging.
  • Replace usage of NetworkHandler and DGuiApplicationHelper with DLog logging and new service manager entry path
  • Add argc/argv checks and subcommand handling: 'dccPlug' shows the simplified DccPluginTestWidget; 'servicemanager' initializes the service manager
  • For 'servicemanager', build the policy configuration path based on SERVICE_CONFIG_DIR and effective UID and initialize ServiceQtDBus with the parsed Policy on the appropriate DBus bus
  • Remove obsolete desktop-centering logic and network handler event filter
example/main.cpp
Introduced a policy-driven DBus service framework for Qt-based services, including service base classes, Qt DBus hooks, and JSON policy parsing.
  • Add Policy class to parse JSON service configuration (name, paths, interfaces, methods, properties, whitelists, visibility, permissions, lifetimes) and expose permission checks for methods and properties
  • Implement ServiceBase as a generic threaded service wrapper that owns a Policy, manages a DBus bus type, and handles idle-time-based auto-unregister via QTimer
  • Implement ServiceQtDBus that derives from ServiceBase, connects to DBus using a service-specific connection, loads a plugin library from SERVICE_LIB_DIR, and invokes DSMRegister/DSMUnRegister entry points to register/unregister service objects
  • Add QTDbusHook singleton and low-level DBus spy/filter hooks to dynamically register service objects on first access, enforce Policy-based permission checks, and support hidden paths and sub-path handling
  • Wire up logging categories for policy, service, and hook components to support debug logging of decisions and errors
example/service/policy.h
example/service/policy.cpp
example/service/servicebase.h
example/service/servicebase.cpp
example/service/serviceqtdbus.h
example/service/serviceqtdbus.cpp
example/service/qtdbushook.h
example/service/qtdbushook.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepin-ci-robot
Copy link

deepin pr auto review

Git Diff 代码审查报告

总体评价

这次代码变更主要涉及从Qt5迁移到Qt6,以及重构了example目录的代码结构。主要变化包括:

  1. 从Qt5迁移到Qt6,包括依赖库的更新
  2. 大幅简化了example测试程序,移除了复杂的UI模块
  3. 新增了服务管理相关的代码,包括策略解析和DBus钩子机制

语法与逻辑审查

CMakeLists.txt

  1. Qt版本迁移

    • 从Qt5迁移到Qt6,更新了CMAKE_PREFIX_PATH和相关依赖包
    • 更新了Dtk和KF5/KF6的依赖
    • 建议:确保所有Qt6相关依赖已正确安装,特别是KF6NetworkManagerQt
  2. 构建配置

    • 添加了条件编译定义,区分Debug和Release模式的路径
    • 建议:考虑使用CMAKE_INSTALL_LIBDIR变量代替硬编码的lib路径,以提高跨平台兼容性
  3. 源文件组织

    • 使用GLOB收集源文件,简化了构建配置
    • 建议:显式列出源文件而非使用GLOB,这样当添加新文件时需要显式修改CMakeLists.txt,更安全

example/dccplugintestwidget.cpp/h

  1. 简化UI

    • 从复杂的DMainWindow继承改为简单的QWidget
    • 移除了大量复杂的UI逻辑和模块管理代码
    • 建议:如果这只是一个测试程序,简化是合理的;但如果是用于演示功能,可能需要保留一些基本功能
  2. 版权年份更新

    • 将版权年份从2018-2022更新为2018-2026
    • 建议:确认2026年是否为预期发布年份,否则使用当前年份更合适

example/main.cpp

  1. 程序入口重构

    • 添加了日志记录功能
    • 添加了服务管理功能,根据参数启动不同模式
    • 建议:添加命令行参数帮助信息,当用户使用未知参数时提供帮助
  2. 服务管理

    • 添加了基于用户权限的服务初始化逻辑
    • 建议:考虑添加错误处理,当配置文件不存在或解析失败时提供友好的错误信息

新增服务相关文件

  1. policy.cpp/h

    • 实现了策略解析和权限检查功能
    • 建议:添加更多错误处理,特别是JSON解析失败的情况
    • 建议:考虑使用QElapsedTimer记录关键操作的性能,以便优化
  2. qtdbushook.cpp/h

    • 实现了DBus钩子机制,用于拦截和检查DBus调用
    • 建议:添加更多日志记录,特别是在权限被拒绝的情况下
    • 建议:考虑添加性能监控,因为钩子机制可能会影响系统性能
  3. servicebase.cpp/h

    • 实现了服务基类,包括服务注册和超时管理
    • 建议:添加更详细的服务状态管理,如启动中、运行中、停止中等状态
  4. serviceqtdbus.cpp/h

    • 实现了基于Qt DBus的服务管理
    • 建议:添加更详细的错误处理,特别是库加载和函数解析失败的情况

代码质量

  1. 命名规范

    • 整体命名规范良好,使用了有意义的变量名和函数名
    • 建议:统一使用驼峰命名法或下划线命名法,避免混用
  2. 注释

    • 部分代码缺少注释,特别是复杂的逻辑部分
    • 建议:为关键函数和复杂逻辑添加详细注释
  3. 代码结构

    • 代码结构清晰,职责分离良好
    • 建议:考虑将一些大函数拆分为更小的函数,提高可读性和可维护性

代码性能

  1. 权限检查

    • Policy::checkPermission函数使用了多层嵌套的QMap查找
    • 建议:考虑使用更高效的数据结构,如哈希表,或者对常用路径进行缓存
  2. DBus钩子

    • 钩子机制会对每个DBus调用进行检查,可能影响性能
    • 建议:添加性能监控,并考虑对高频调用进行优化
  3. JSON解析

    • Policy::parseConfig函数在启动时解析JSON文件
    • 建议:考虑缓存解析结果,避免重复解析

代码安全

  1. 权限检查

    • 实现了基于进程的权限检查机制
    • 建议:添加对进程ID的验证,防止进程ID欺骗
  2. DBus通信

    • 实现了DBus调用的拦截和权限验证
    • 建议:添加对恶意调用的防护,如频率限制
  3. 文件操作

    • 读取配置文件和库文件
    • 建议:添加对文件权限和完整性的检查,防止篡改
  4. 输入验证

    • 部分函数缺少输入参数验证
    • 建议:对所有公共函数添加参数验证,防止无效输入导致的问题

具体改进建议

  1. CMakeLists.txt

    # 使用CMAKE_INSTALL_LIBDIR变量代替硬编码路径
    ADD_DEFINITIONS(-DSERVICE_LIB_DIR="${CMAKE_INSTALL_FULL_LIBDIR}/deepin-service-manager/")
    
    # 显式列出源文件而非使用GLOB
    set(COMPILEFILES
        main.cpp
        dccplugintestwidget.cpp
        service/policy.cpp
        service/qtdbushook.cpp
        service/servicebase.cpp
        service/serviceqtdbus.cpp
    )
  2. policy.cpp

    // 添加JSON解析错误处理
    if (!readJsonFile(jsonDoc, path)) {
        qCWarning(dsm_policy) << "Failed to read or parse JSON file:" << path;
        // 添加更详细的错误信息,如文件是否存在、权限是否足够等
        return;
    }
    
    // 添加性能监控
    QElapsedTimer timer;
    timer.start();
    // ... 解析逻辑 ...
    qCDebug(dsm_policy) << "JSON parsing took" << timer.elapsed() << "milliseconds";
  3. qtdbushook.cpp

    // 添加更详细的日志记录
    if (!serviceObj->policy->checkMethodPermission(getCMD(serviceObj, msg.service()),
                                                    realPath,
                                                    msg.interface(),
                                                    msg.member())) {
        qCWarning(dsm_hook_qt) << "Permission denied for method call:"
                              << msg.interface() << "::" << msg.member()
                              << "from process:" << getCMD(serviceObj, msg.service());
        QDBusMessage reply =
                msg.createErrorReply("com.deepin.service.Permission.Deny", "The call is deny2");
        // ...
    }
  4. main.cpp

    // 添加命令行参数帮助
    void printHelp(const char *programName) {
        qDebug() << "Usage:" << programName << "[command]";
        qDebug() << "Commands:";
        qDebug() << "  dccPlug       Run DCC plugin test widget";
        qDebug() << "  servicemanager Run service manager";
        qDebug() << "  help          Print this help message";
    }
    
    // 在main函数中添加帮助信息
    if (argc > 1) {
        if (QString(argv[1]) == "help" || QString(argv[1]) == "--help" || QString(argv[1]) == "-h") {
            printHelp(argv[0]);
            return 0;
        }
        // ... 其他命令处理 ...
    } else {
        printHelp(argv[0]);
        return 1;
    }

总结

这次代码变更主要完成了从Qt5到Qt6的迁移,并重构了example目录的代码结构。整体上代码质量良好,但在错误处理、性能优化和安全性方面还有改进空间。建议在后续开发中:

  1. 加强错误处理和日志记录
  2. 添加性能监控和优化
  3. 增强安全性,特别是权限检查和输入验证
  4. 提高代码可读性和可维护性,通过添加注释和优化代码结构

这些改进将有助于提高代码的健壮性、性能和安全性。

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues, and left some high level feedback:

  • In main.cpp, the Policy policy(nullptr); created on the stack is passed into ServiceQtDBus::init, which calls p->setParent(this) and transfers ownership to a heap QObject; this will lead to invalid QObject parenting and potential double-free when policy goes out of scope, so either allocate Policy on the heap or avoid setting its parent here.
  • In Policy::parsePolicyPath, the subpath flag is read from JSON but mapSubPath.insert(path, pathHide); stores pathHide instead of subpath, which means the subpath configuration is never respected; this should insert the subpath value.
  • In qtdbushook.cpp, the error reply path in the non-filter build (QTDBusSpyHook) uses QDBusConnection::connectToBus(QDBusConnection::SessionBus, QString("org.dsdsf.dsfsdf")) instead of the service connection used elsewhere, which looks like leftover test code and likely prevents the error reply from reaching the caller; it should mirror the normal send path via the existing bus connection.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In main.cpp, the `Policy policy(nullptr);` created on the stack is passed into `ServiceQtDBus::init`, which calls `p->setParent(this)` and transfers ownership to a heap QObject; this will lead to invalid QObject parenting and potential double-free when `policy` goes out of scope, so either allocate `Policy` on the heap or avoid setting its parent here.
- In Policy::parsePolicyPath, the `subpath` flag is read from JSON but `mapSubPath.insert(path, pathHide);` stores `pathHide` instead of `subpath`, which means the subpath configuration is never respected; this should insert the `subpath` value.
- In qtdbushook.cpp, the error reply path in the non-filter build (`QTDBusSpyHook`) uses `QDBusConnection::connectToBus(QDBusConnection::SessionBus, QString("org.dsdsf.dsfsdf"))` instead of the service connection used elsewhere, which looks like leftover test code and likely prevents the error reply from reaching the caller; it should mirror the normal send path via the existing bus connection.

## Individual Comments

### Comment 1
<location> `example/service/policy.cpp:325-327` </location>
<code_context>
+    jsonGetBool(obj, "pathhide", pathHide, false);
+    mapPathHide.insert(path, pathHide);
+
+    bool subpath;
+    jsonGetBool(obj, "subpath", subpath, false);
+    mapSubPath.insert(path, pathHide);
+
+    PolicyPath policyPath;
</code_context>

<issue_to_address>
**issue (bug_risk):** Bug: `subpath` flag is read but `pathHide` is stored into `mapSubPath` instead.

In `parsePolicyPath`, you read `subpath` but then insert `pathHide` into `mapSubPath`, so the `subpath` flag is never used and `allowSubPath()` will behave incorrectly. This should be `mapSubPath.insert(path, subpath);`.
</issue_to_address>

### Comment 2
<location> `example/service/qtdbushook.cpp:84-93` </location>
<code_context>
+                                       .arg(serviceObj->policy->idleTime);
+        QTimer::singleShot(0, serviceObj, SLOT(restartTimer()));
+    }
+    if (msg.member() == "Introspect" && msg.interface() == "org.freedesktop.DBus.Introspectable") {
+        if (serviceObj->policy->checkPathHide(realPath)) {
+            qCDebug(dsm_hook_qt) << "--call Introspect" << msg.path() << " ,is hided!";
+            QList<QVariant> arguments;
+            arguments << "";
+            QDBusMessage reply = msg.createReply(arguments);
+            ServiceQtDBus *srv = qobject_cast<ServiceQtDBus *>(serviceObj);
+            if (srv) {
+                srv->qDbusConnection().send(reply);
+            }
+            //            ((ServiceQtDBus*)serviceObj)->qDbusConnection().send(reply);
+        }
+    } else if (msg.member() == "Set" && msg.interface() == "org.freedesktop.DBus.Properties") {
</code_context>

<issue_to_address>
**issue (bug_risk):** Hidden Introspect replies are sent but the original call is not blocked in the filter hook.

In `QTDBusHook`, when `Introspect` is handled and `checkPathHide(realPath)` is true, you send a custom reply but still return `0`, so the original call proceeds and may still return real introspection data. After sending the reply, return a failure code (e.g. `-1`) to stop the original handling and actually hide the path.
</issue_to_address>

### Comment 3
<location> `example/service/qtdbushook.cpp:125-126` </location>
<code_context>
+            if (srv) {
+                // srv->qDbusConnection().send(reply);
+                // QDBusConnection::sessionBus().send(reply);
+                QDBusConnection::connectToBus(QDBusConnection::SessionBus,
+                                              QString("org.dsdsf.dsfsdf"))
+                        .send(reply);
+                return;
</code_context>

<issue_to_address>
**issue (bug_risk):** Error replies are sent on a new, hard-coded DBus connection instead of the correct bus.

In `QTDBusSpyHook`, the error reply for denied calls is sent via `QDBusConnection::connectToBus(SessionBus, "org.dsdsf.dsfsdf")`, which creates/uses an unrelated connection, so the reply likely never reaches the original caller. Instead, send the reply on the same connection as the service (e.g. `srv->qDbusConnection().send(reply);`) and remove the dummy bus connection/commented-out code.
</issue_to_address>

### Comment 4
<location> `example/service/servicebase.cpp:43-47` </location>
<code_context>
+
+void ServiceBase::initService()
+{
+    QThread *th = new QThread();
+    setParent(nullptr);
+    moveToThread(th);
+    connect(th, &QThread::started, this, &ServiceBase::initThread);
+    th->start();
+}
+
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Service threads are created without ownership or shutdown, which can leak threads or keep the process alive.

`initService` allocates a `QThread` with `new` and no parent, and there’s no visible shutdown or deletion path. This risks leaking threads and preventing clean application exit. Please ensure the thread is stopped when the service is unregistered/app exits, give it appropriate ownership, and connect `QThread::finished` to `deleteLater()` for cleanup.

Suggested implementation:

```cpp
#include "servicebase.h"

#include "policy.h"

#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>
#include <QThread>
#include <QTimer>

void ServiceBase::initService()
{
    // Create a dedicated thread for this service with proper ownership
    QThread *th = new QThread(this);
    // If the class has a thread member, e.g. QThread *m_thread, assign it here:
    // m_thread = th;

    // Move this service object to the worker thread
    moveToThread(th);

    // Initialize the service when the thread starts
    connect(th, &QThread::started, this, &ServiceBase::initThread);

    // Ensure the thread object is deleted when it finishes
    connect(th, &QThread::finished, th, &QObject::deleteLater);

    th->start();
}

```

To fully implement safe shutdown and avoid keeping the process alive, you should also:

1. In `servicebase.h`:
   - Add a member to track the thread if it does not already exist:
     `QThread *m_thread = nullptr;`
   - Update `initService()` to assign `m_thread = th;` as commented in the implementation above.

2. In the destructor `ServiceBase::~ServiceBase()` (or wherever the service is unregistered, e.g. `unregisterService()`):
   - Ensure the thread is asked to stop and waited on before destruction:
   ```cpp
   if (m_thread) {
       m_thread->quit();
       m_thread->wait();
       // m_thread will delete itself due to finished -> deleteLater connection
       m_thread = nullptr;
   }
   ```
   - Make sure this code runs before the `ServiceBase` object is destroyed or before the application exits.

3. If multiple services share a thread or if the application has a central thread manager, adapt the ownership and shutdown logic accordingly so that the thread is not deleted while other objects still use it.
</issue_to_address>

### Comment 5
<location> `example/service/qtdbushook.cpp:56` </location>
<code_context>
+}
+
+// if it is not a local message, hook exec at main thread
+void QTDBusSpyHook(const QDBusMessage &msg)
+{
+    qCInfo(dsm_hook_qt) << "--msg=" << msg;
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting the shared DBus handling logic and simplifying `getServiceObject`’s interface to eliminate duplication and unused parameters while keeping current behavior intact.

You can significantly reduce complexity here without changing behavior by:

### 1. Deduplicating `QTDBusSpyHook` and `QTDBusHook`

Almost all of this code is copy‑pasted between the two hooks. You can centralize the common logic into a small helper and keep the per‑hook differences explicit.

One approach is to extract:

- Common: object lookup, registration, timer management, policy checks.
- Variant: how denial is signaled (send reply via which connection, return code, the special `connectToBus` in the spy hook).

For example:

```cpp
enum class HookResult {
    Allow,
    DenyWithReply,     // reply already sent
    NoServiceObject    // object not found or unusable
};

static HookResult handleDbusMessage(ServiceBase *serviceObj,
                                    const QDBusMessage &msg,
                                    const QString &realPath,
                                    bool useWeirdSpyDenyBehavior)
{
    if (!serviceObj->isRegister()) {
        qCInfo(dsm_hook_qt) << "--to register dbus object: " << msg.path();
        serviceObj->registerService();
    }

    if (!serviceObj->policy->isResident() && !serviceObj->isLockTimer()) {
        qCInfo(dsm_hook_qt) << QString("--service: %1 will unregister in %2 minutes!")
                                       .arg(serviceObj->policy->name)
                                       .arg(serviceObj->policy->idleTime);
        QTimer::singleShot(0, serviceObj, SLOT(restartTimer()));
    }

    ServiceQtDBus *srv = qobject_cast<ServiceQtDBus *>(serviceObj);
    if (!srv)
        return HookResult::Allow;

    // Introspect
    if (msg.member() == "Introspect"
        && msg.interface() == "org.freedesktop.DBus.Introspectable") {
        if (serviceObj->policy->checkPathHide(realPath)) {
            qCDebug(dsm_hook_qt) << "--call Introspect" << msg.path() << " ,is hided!";
            QList<QVariant> arguments;
            arguments << "";
            QDBusMessage reply = msg.createReply(arguments);
            srv->qDbusConnection().send(reply);
            return HookResult::DenyWithReply;
        }
        return HookResult::Allow;
    }

    // Properties.Set
    if (msg.member() == "Set"
        && msg.interface() == "org.freedesktop.DBus.Properties") {
        const QList<QVariant> &args = msg.arguments();
        if (args.size() >= 2) {
            if (!serviceObj->policy->checkPropertyPermission(
                        getCMD(serviceObj, msg.service()),
                        realPath,
                        args.at(0).toString(),
                        args.at(1).toString())) {
                QDBusMessage reply = msg.createErrorReply(
                        "com.deepin.service.Permission.Deny", "The call is deny");
                srv->qDbusConnection().send(reply);
                return HookResult::DenyWithReply;
            }
        }
        return HookResult::Allow;
    }

    // Normal methods
    if (msg.interface() != "org.freedesktop.DBus.Properties"
        && msg.interface() != "org.freedesktop.DBus.Introspectable"
        && msg.interface() != "org.freedesktop.DBus.Peer")) {
        if (!serviceObj->policy->checkMethodPermission(
                    getCMD(serviceObj, msg.service()),
                    realPath,
                    msg.interface(),
                    msg.member())) {
            QDBusMessage reply = msg.createErrorReply(
                    "com.deepin.service.Permission.Deny", "The call is deny2");

            if (useWeirdSpyDenyBehavior) {
                // preserves current spy behavior
                QDBusConnection::connectToBus(QDBusConnection::SessionBus,
                                              QStringLiteral("org.dsdsf.dsfsdf"))
                        .send(reply);
            } else {
                srv->qDbusConnection().send(reply);
            }

            return HookResult::DenyWithReply;
        }
    }

    return HookResult::Allow;
}
```

Then each hook becomes much smaller and easier to reason about:

```cpp
void QTDBusSpyHook(const QDBusMessage &msg)
{
    ServiceBase *serviceObj = nullptr;
    bool isSubPath;
    QString realPath;

    if (!QTDbusHook::instance()->getServiceObject(
                QString(), msg.path(), &serviceObj, isSubPath, realPath)) {
        qCWarning(dsm_hook_qt) << "--can not find hook object: " << msg.path();
        return;
    }

    const auto result = handleDbusMessage(serviceObj, msg, realPath,
                                          /*useWeirdSpyDenyBehavior=*/true);
    Q_UNUSED(result); // spy hook does not need to return anything
}
```

```cpp
int QTDBusHook(const QString &baseService, const QDBusMessage &msg)
{
    Q_UNUSED(baseService);

    ServiceBase *serviceObj = nullptr;
    bool isSubPath;
    QString realPath;

    if (!QTDbusHook::instance()->getServiceObject(
                QString(), msg.path(), &serviceObj, isSubPath, realPath)) {
        qCWarning(dsm_hook_qt) << "--can not find hook object:" << msg.path();
        return 0;
    }

    const auto result = handleDbusMessage(serviceObj, msg, realPath,
                                          /*useWeirdSpyDenyBehavior=*/false);
    if (result == HookResult::DenyWithReply)
        return -1;
    return 0;
}
```

This keeps *all* current behaviors (including the unusual `connectToBus` path) but removes the large duplicated if‑else trees.

### 2. Clarifying / removing `isSubPath`

`getServiceObject` writes `isSubPath`, but in this file both call sites ignore it:

```cpp
bool isSubPath;
QString realPath;
QTDbusHook::instance()->getServiceObject("", msg.path(), &serviceObj, isSubPath, realPath);
```

To reduce mental overhead:

- If `isSubPath` is not used anywhere else, drop it from the signature and implementation:

```cpp
bool QTDbusHook::getServiceObject(QString name,
                                  QString path,
                                  ServiceBase **service,
                                  QString &realPath)
{
    Q_UNUSED(name);
    auto iterService = m_serviceMap.find(path);
    if (iterService != m_serviceMap.end()) {
        *service = iterService.value();
        realPath = iterService.key();
        return true;
    }
    for (auto iter = m_serviceMap.begin(); iter != m_serviceMap.end(); ++iter) {
        if (path.startsWith(iter.key()) && iter.value()->policy->allowSubPath(iter.key())) {
            *service = iter.value();
            realPath = iter.key();
            return true;
        }
    }
    return false;
}
```

- And adapt call sites accordingly:

```cpp
ServiceBase *serviceObj = nullptr;
QString realPath;
bool findRet = QTDbusHook::instance()->getServiceObject(
        QString(), msg.path(), &serviceObj, realPath);
```

- If `isSubPath` is needed by other users of `getServiceObject`, consider either:
  - using it in these hooks as well (and naming it clearly, e.g. `matchedExactPath`), or  
  - splitting into two overloads: one simple version for hooks, one detailed version for callers that care about the distinction.

### 3. Normalizing denial reply behavior

Right now, denial replies are constructed in multiple places with slightly different strings and sending mechanisms (including commented‑out code and the debug‑looking `org.dsdsf.dsfsdf` connection).

The shared helper above already centralizes:

- the error names,
- the messages,
- which connection is used.

If the odd `connectToBus` is intentional, keeping it behind a `useWeirdSpyDenyBehavior` flag makes that intent explicit and localizes the “special case,” instead of inlining it in a big branch. If it’s not intentional, this helper makes it very easy to swap it out in one place later without touching both hooks.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +325 to +327
bool subpath;
jsonGetBool(obj, "subpath", subpath, false);
mapSubPath.insert(path, pathHide);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Bug: subpath flag is read but pathHide is stored into mapSubPath instead.

In parsePolicyPath, you read subpath but then insert pathHide into mapSubPath, so the subpath flag is never used and allowSubPath() will behave incorrectly. This should be mapSubPath.insert(path, subpath);.

Comment on lines +84 to +93
if (msg.member() == "Introspect" && msg.interface() == "org.freedesktop.DBus.Introspectable") {
if (serviceObj->policy->checkPathHide(realPath)) {
qCDebug(dsm_hook_qt) << "--call Introspect" << msg.path() << " ,is hided!";
QList<QVariant> arguments;
arguments << "";
QDBusMessage reply = msg.createReply(arguments);
ServiceQtDBus *srv = qobject_cast<ServiceQtDBus *>(serviceObj);
if (srv) {
srv->qDbusConnection().send(reply);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Hidden Introspect replies are sent but the original call is not blocked in the filter hook.

In QTDBusHook, when Introspect is handled and checkPathHide(realPath) is true, you send a custom reply but still return 0, so the original call proceeds and may still return real introspection data. After sending the reply, return a failure code (e.g. -1) to stop the original handling and actually hide the path.

Comment on lines +125 to +126
QDBusConnection::connectToBus(QDBusConnection::SessionBus,
QString("org.dsdsf.dsfsdf"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Error replies are sent on a new, hard-coded DBus connection instead of the correct bus.

In QTDBusSpyHook, the error reply for denied calls is sent via QDBusConnection::connectToBus(SessionBus, "org.dsdsf.dsfsdf"), which creates/uses an unrelated connection, so the reply likely never reaches the original caller. Instead, send the reply on the same connection as the service (e.g. srv->qDbusConnection().send(reply);) and remove the dummy bus connection/commented-out code.

Comment on lines +43 to +47
QThread *th = new QThread();
setParent(nullptr);
moveToThread(th);
connect(th, &QThread::started, this, &ServiceBase::initThread);
th->start();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Service threads are created without ownership or shutdown, which can leak threads or keep the process alive.

initService allocates a QThread with new and no parent, and there’s no visible shutdown or deletion path. This risks leaking threads and preventing clean application exit. Please ensure the thread is stopped when the service is unregistered/app exits, give it appropriate ownership, and connect QThread::finished to deleteLater() for cleanup.

Suggested implementation:

#include "servicebase.h"

#include "policy.h"

#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>
#include <QThread>
#include <QTimer>

void ServiceBase::initService()
{
    // Create a dedicated thread for this service with proper ownership
    QThread *th = new QThread(this);
    // If the class has a thread member, e.g. QThread *m_thread, assign it here:
    // m_thread = th;

    // Move this service object to the worker thread
    moveToThread(th);

    // Initialize the service when the thread starts
    connect(th, &QThread::started, this, &ServiceBase::initThread);

    // Ensure the thread object is deleted when it finishes
    connect(th, &QThread::finished, th, &QObject::deleteLater);

    th->start();
}

To fully implement safe shutdown and avoid keeping the process alive, you should also:

  1. In servicebase.h:

    • Add a member to track the thread if it does not already exist:
      QThread *m_thread = nullptr;
    • Update initService() to assign m_thread = th; as commented in the implementation above.
  2. In the destructor ServiceBase::~ServiceBase() (or wherever the service is unregistered, e.g. unregisterService()):

    • Ensure the thread is asked to stop and waited on before destruction:
    if (m_thread) {
        m_thread->quit();
        m_thread->wait();
        // m_thread will delete itself due to finished -> deleteLater connection
        m_thread = nullptr;
    }
    • Make sure this code runs before the ServiceBase object is destroyed or before the application exits.
  3. If multiple services share a thread or if the application has a central thread manager, adapt the ownership and shutdown logic accordingly so that the thread is not deleted while other objects still use it.

}

// if it is not a local message, hook exec at main thread
void QTDBusSpyHook(const QDBusMessage &msg)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider extracting the shared DBus handling logic and simplifying getServiceObject’s interface to eliminate duplication and unused parameters while keeping current behavior intact.

You can significantly reduce complexity here without changing behavior by:

1. Deduplicating QTDBusSpyHook and QTDBusHook

Almost all of this code is copy‑pasted between the two hooks. You can centralize the common logic into a small helper and keep the per‑hook differences explicit.

One approach is to extract:

  • Common: object lookup, registration, timer management, policy checks.
  • Variant: how denial is signaled (send reply via which connection, return code, the special connectToBus in the spy hook).

For example:

enum class HookResult {
    Allow,
    DenyWithReply,     // reply already sent
    NoServiceObject    // object not found or unusable
};

static HookResult handleDbusMessage(ServiceBase *serviceObj,
                                    const QDBusMessage &msg,
                                    const QString &realPath,
                                    bool useWeirdSpyDenyBehavior)
{
    if (!serviceObj->isRegister()) {
        qCInfo(dsm_hook_qt) << "--to register dbus object: " << msg.path();
        serviceObj->registerService();
    }

    if (!serviceObj->policy->isResident() && !serviceObj->isLockTimer()) {
        qCInfo(dsm_hook_qt) << QString("--service: %1 will unregister in %2 minutes!")
                                       .arg(serviceObj->policy->name)
                                       .arg(serviceObj->policy->idleTime);
        QTimer::singleShot(0, serviceObj, SLOT(restartTimer()));
    }

    ServiceQtDBus *srv = qobject_cast<ServiceQtDBus *>(serviceObj);
    if (!srv)
        return HookResult::Allow;

    // Introspect
    if (msg.member() == "Introspect"
        && msg.interface() == "org.freedesktop.DBus.Introspectable") {
        if (serviceObj->policy->checkPathHide(realPath)) {
            qCDebug(dsm_hook_qt) << "--call Introspect" << msg.path() << " ,is hided!";
            QList<QVariant> arguments;
            arguments << "";
            QDBusMessage reply = msg.createReply(arguments);
            srv->qDbusConnection().send(reply);
            return HookResult::DenyWithReply;
        }
        return HookResult::Allow;
    }

    // Properties.Set
    if (msg.member() == "Set"
        && msg.interface() == "org.freedesktop.DBus.Properties") {
        const QList<QVariant> &args = msg.arguments();
        if (args.size() >= 2) {
            if (!serviceObj->policy->checkPropertyPermission(
                        getCMD(serviceObj, msg.service()),
                        realPath,
                        args.at(0).toString(),
                        args.at(1).toString())) {
                QDBusMessage reply = msg.createErrorReply(
                        "com.deepin.service.Permission.Deny", "The call is deny");
                srv->qDbusConnection().send(reply);
                return HookResult::DenyWithReply;
            }
        }
        return HookResult::Allow;
    }

    // Normal methods
    if (msg.interface() != "org.freedesktop.DBus.Properties"
        && msg.interface() != "org.freedesktop.DBus.Introspectable"
        && msg.interface() != "org.freedesktop.DBus.Peer")) {
        if (!serviceObj->policy->checkMethodPermission(
                    getCMD(serviceObj, msg.service()),
                    realPath,
                    msg.interface(),
                    msg.member())) {
            QDBusMessage reply = msg.createErrorReply(
                    "com.deepin.service.Permission.Deny", "The call is deny2");

            if (useWeirdSpyDenyBehavior) {
                // preserves current spy behavior
                QDBusConnection::connectToBus(QDBusConnection::SessionBus,
                                              QStringLiteral("org.dsdsf.dsfsdf"))
                        .send(reply);
            } else {
                srv->qDbusConnection().send(reply);
            }

            return HookResult::DenyWithReply;
        }
    }

    return HookResult::Allow;
}

Then each hook becomes much smaller and easier to reason about:

void QTDBusSpyHook(const QDBusMessage &msg)
{
    ServiceBase *serviceObj = nullptr;
    bool isSubPath;
    QString realPath;

    if (!QTDbusHook::instance()->getServiceObject(
                QString(), msg.path(), &serviceObj, isSubPath, realPath)) {
        qCWarning(dsm_hook_qt) << "--can not find hook object: " << msg.path();
        return;
    }

    const auto result = handleDbusMessage(serviceObj, msg, realPath,
                                          /*useWeirdSpyDenyBehavior=*/true);
    Q_UNUSED(result); // spy hook does not need to return anything
}
int QTDBusHook(const QString &baseService, const QDBusMessage &msg)
{
    Q_UNUSED(baseService);

    ServiceBase *serviceObj = nullptr;
    bool isSubPath;
    QString realPath;

    if (!QTDbusHook::instance()->getServiceObject(
                QString(), msg.path(), &serviceObj, isSubPath, realPath)) {
        qCWarning(dsm_hook_qt) << "--can not find hook object:" << msg.path();
        return 0;
    }

    const auto result = handleDbusMessage(serviceObj, msg, realPath,
                                          /*useWeirdSpyDenyBehavior=*/false);
    if (result == HookResult::DenyWithReply)
        return -1;
    return 0;
}

This keeps all current behaviors (including the unusual connectToBus path) but removes the large duplicated if‑else trees.

2. Clarifying / removing isSubPath

getServiceObject writes isSubPath, but in this file both call sites ignore it:

bool isSubPath;
QString realPath;
QTDbusHook::instance()->getServiceObject("", msg.path(), &serviceObj, isSubPath, realPath);

To reduce mental overhead:

  • If isSubPath is not used anywhere else, drop it from the signature and implementation:
bool QTDbusHook::getServiceObject(QString name,
                                  QString path,
                                  ServiceBase **service,
                                  QString &realPath)
{
    Q_UNUSED(name);
    auto iterService = m_serviceMap.find(path);
    if (iterService != m_serviceMap.end()) {
        *service = iterService.value();
        realPath = iterService.key();
        return true;
    }
    for (auto iter = m_serviceMap.begin(); iter != m_serviceMap.end(); ++iter) {
        if (path.startsWith(iter.key()) && iter.value()->policy->allowSubPath(iter.key())) {
            *service = iter.value();
            realPath = iter.key();
            return true;
        }
    }
    return false;
}
  • And adapt call sites accordingly:
ServiceBase *serviceObj = nullptr;
QString realPath;
bool findRet = QTDbusHook::instance()->getServiceObject(
        QString(), msg.path(), &serviceObj, realPath);
  • If isSubPath is needed by other users of getServiceObject, consider either:
    • using it in these hooks as well (and naming it clearly, e.g. matchedExactPath), or
    • splitting into two overloads: one simple version for hooks, one detailed version for callers that care about the distinction.

3. Normalizing denial reply behavior

Right now, denial replies are constructed in multiple places with slightly different strings and sending mechanisms (including commented‑out code and the debug‑looking org.dsdsf.dsfsdf connection).

The shared helper above already centralizes:

  • the error names,
  • the messages,
  • which connection is used.

If the odd connectToBus is intentional, keeping it behind a useWeirdSpyDenyBehavior flag makes that intent explicit and localizes the “special case,” instead of inlining it in a big branch. If it’s not intentional, this helper makes it very easy to swap it out in one place later without touching both hooks.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caixr23, ut003640

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ut003640
Copy link
Contributor Author

/forcemerge

@deepin-bot
Copy link
Contributor

deepin-bot bot commented Jan 29, 2026

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit 60f1bb7 into linuxdeepin:master Jan 29, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants