本篇文章将介绍 Deepin 后端服务框架如何使用,以及接口、配置的实现。
- 插件服务,把服务以一个插件方式加载运行
- dbus 接口私有化(接口隐藏、接口白名单)
- dbus 服务的按需启动
- 独立应用的 dbus 接口私有化 sdk
graph LR
A([systemd]) --> B(service-manager)
B --> C(read json)
C --> D(start group unit)
D --> E(group1 process)
D --> F(group2 process)
D --> G(groupN process)
E --> H(load group1 plugins)
F --> I(load group2 plugins)
G --> J(load groupN plugins)
H --> K([end])
I --> K
J --> K
- 如图所示,deepin-service-manager 由 systemd 服务拉起来;
- service 起来后读取所有的 json 配置文件,根据配置文件进行分组;
- 按照分好的组通过 systemd 启动子进程实例,并传入组名;
- 子进程启动,按照传入的组名进行过滤,注册该组的服务,并加载服务插件。
graph LR
A([process]) --> B(read json)
B --> C(register dbus)
C --> D(hook dbus)
D --> E{config policy?}
E -->|yes| F(do nothing)
E -->|no| G("call real dbus")
F --> H([end])
G --> H
- 进程在启动后会到特定目录读取 json 配置
- 注册 dbus 服务时,会对 dbus 进行 hook 操作
- 当 dbus 接口被调用时,hook 方法会进行拦截,此时根据配置文件中的 policy 配置决定是否继续调用真正的接口
graph LR
A([QDBusService]) --> B(read json)
B --> D(hook dbus)
D --> E{is Resident?}
E -->|yes| F("call real dbus")
E -->|no| G(start timer)
G --> F
G --> H(timeout)
H --> J(quit app)
J --> K([end])
F --> K
- 独立应用需继承 QDBusService 类
- 初始化时需指定 json 配置文件,QDBusService 会进行 hook dbus 操作
- 当配置中指定了按需启动,则在接口被调用时启动定时器,超时即退出进程
{
"name": "org.deepin.service.demo",
"pluginPath": "demo.so",
"group": "app",
"pluginType": "qt",
"version": "1.0",
"startType": "Resident",
"idleTime": 10,
"dependencies": [],
"startDelay": 0,
"policy": [
{
"path": "/org/deepin/service/demo1"
},
{
"path": "/org/deepin/service/demo2"
}
]
}
- name: [必选]dbus name,框架中会注册该 name
- pluginPath: [必选]插件 so 名称
- group: [可选]插件按进程分组 core|dde|app,默认分组为 app
- pluginType: [可选]插件类型,暂时只有 qt 和 sd 两种,默认为 qt
- version: [可选]配置文件版本,预留配置,无实际用途
- startType: [可选]启动方式,Resident(常驻)、OnDemand(按需启动)。默认 Resident。
- idleTime: [可选]若服务是按需启动,则可以设置闲时时间,超时则会退出当前进程,单位为分钟
- dependencies: [可选]若依赖其他服务,可将服务名填在此处,在依赖启动之前不会启动此服务
- startDelay: [可选]若需要延时启动,可将延时时间填在此处,单位为秒
- policy: [可选]对于按需启动的插件是必选项,配置了哪些 Path 可以被捕获,只有捕获了这些 Path 才会启动定时器和重置定时器。
配置文件中必选字段为必须要填写字段,否则插件无法正常启动,可选字段可视情况选择填写即可!
配置文件安装路径规则:
system:
/usr/share/deepin-service-manager/system/demo.json
session:
/usr/share/deepin-service-manager/user/demo.json
-
qdbus
#include <QDBusConnection> #include "service.h" // 实现的dbusobject,基本支持qdbus原规则 static Service *service = nullptr; // name:dbus name,配置文件中的"name", // data:自定义数据 extern "C" int DSMRegister(const char *name, void *data) { (void)name; service = new Service(); QDBusConnection::RegisterOptions opts = QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllProperties; auto connection = reinterpret_cast<QDBusConnection *>(data); connection->registerObject("/org/deepin/services/demo1", service, opts); return 0; } // 插件卸载时,若需要释放资源请在此实现 extern "C" int DSMUnRegister(const char *name, void *data) { (void)name; (void)data; service->deleteLater(); service = nullptr; return 0; }
-
sdbus
#include "service.h" extern "C" int DSMRegister(const char *name, void *data) { (void)name; if (!data) { return -1; } sd_bus *bus = (sd_bus *)data; sd_bus_slot *slot = NULL; if (sd_bus_add_object_vtable(bus, &slot, "/org/deepin/service/sdbus/demo1", "org.deepin.service.sdbus.demo1", calculator_vtable, NULL) < 0) { return -1; } return 0; } extern "C" int DSMUnRegister(const char *name, void *data) { (void)name; (void)data; return 0; }
实现的 so 文件安装路径为 ${CMAKE_INSTALL_LIBDIR}/deepin-service-manager/
不同平台的 lib 路径可能不一样,推荐使用GNUInstallDirs
{
"name": "org.deepin.service.demo",
"version": "1.0",
"startType": "Resident",
"idleTime": 10
}
- name: [必选]dbus name,框架中会注册该 name
- version: [可选]配置文件版本,预留配置,无实际用途
- startType: [可选]启动方式,Resident(常驻)、OnDemand(按需启动),默认 Resident。若设置 OnDaemand,则需要设置 idleTime 字段!
- idleTime: [可选]若服务是按需启动,则可以设置闲时时间,超时则会退出当前进程,单位为分钟。
独立应用的配置文件与插件的配置文件很多地方不一样,插件中的很多配置,在独立应用中是不生效的!
配置文件安装路径规则:
/usr/share/deepin-service-manager/other/demo.json
QDBusService
该类实现了 dbus 权限管控和闲时退出功能,使用该类非常自由:
- 可以继承该类
- 可以单独创建该类实例
-
DBus Interface 类继承该类:
class ServiceInterface : public QDBusService { Q_OBJECT Q_CLASSINFO("D-Bus Interface", ServiceInterfaceStr); public: explicit ServiceInterface(QObject *parent = nullptr); void init(const QDBusConnection::BusType &type) { initPolicy(type, QString(SERVICE_CONFIG_DIR) + "other/demo.json"); } };
然后直接创建该类实例,并用该类进行 DBus 注册即可,如:
if (!connection.registerObject(ServiceInterfacePath, serviceInterfaceInstance, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAllProperties)) { qWarning() << "[ServiceManager]failed to register dbus object: " << connection.lastError().message(); }
-
使用 DBus Adaptor 方式进行注册:
class Service : public QDBusService, protected QDBusContext { Q_OBJECT public: explicit Service(QObject *parent = nullptr) { initPolicy(QDBusConnection::SessionBus, QString(SERVICE_CONFIG_DIR) + "other/demo.json"); } Q_PROPERTY(QString msg READ Msg WRITE SetMsg NOTIFY MsgChanged) QString Msg(); void SetMsg(QString value); Q_SIGNALS: // xml - signal void MsgChanged(const QString &msg); public Q_SLOTS: // xml - method QString Hello(); private: QString m_msg; };
然后使用 adaptor 进行注册:
Service s; DemoAdaptor adp(&s); // 从QDBusService对象拿到 QDBusConnection 防止注册对象不一致,导致无法正常管理权限 QDBusConnection connection = s.qDbusConnection(); if (!connection.registerObject("/org/deepin/service/demo", &s)) { qWarning() << "failed to register dbus object" << connection.lastError().message(); }
-
单独使用该类:
QDBusService service; service.initPolicy(QDBusConnection::SessionBus, QString(SERVICE_CONFIG_DIR) + "other/demo.json");
可以将这段代码放在任何类里。
独立应用若需要按需启动,需要安装 DBus service 文件,注意
Exec
字段要填写自己可执行程序的路径!
QDBusService::initPolicy(const QDBusConnection::BusType&, const QString&)
初始化配置,需要指定 DBus 类型和配置文件路径。
QDBusService::qDbusConnection()
获取 QDBusConnection 对象,为保持服务总线在一个对象上,务必不要使用默认的 QDBusConnection,而是从这里获取。
QDBusService::lockTimer(bool)
是否锁定计时器,若锁定计时器,则在解锁前不会执行闲时退出操作。参数为true
是锁定,否则解锁。
- 插件统一由主框架进行管理,独立应用由应用自己管理
- 插件提供进程合并的机制,独立应用没有
- 插件适合纯后端服务,独立应用可以是后端服务,也可以是前端 ui 程序
- 主服务会根据配置对插件进行一些辅助功能添加,独立应用只需继承类,尽量不改变原有 DBus 注册方式(所以独立应用中需要自己 registerService,而插件不需要)
- 插件只能由配置字段来进行控制,独立应用可以通过配置+接口的方式进行控制,所以更加灵活
在原来的配置文件增加私有化规则即可。
{
...
"whitelists": [
{
"name": "w1",
"process": ["/usr/bin/aaa", "/usr/bin/bbb"]
},
{
"name": "w2",
"process": ["/usr/bin/aaa", "/usr/bin/ccc", "/usr/bin/python3"]
},
{
"name": "all",
"description": "No configuration is required, which means no restrictions"
}
],
"policy": [
{
"path": "/qdbus/demo1",
"pathhide": true,
"permission": true,
"subpath": true,
"whitelist": "w1",
"interfaces": [
{
"interface": "org.deepin.service.demo",
"whitelist": "w1",
// "permission":true,
"methods": [
{
"method": "Multiply",
"whitelist": "w2"
}
],
"properties": [
{
"property": "Age",
"permission": false
}
]
}
]
},
{
"path": "/qdbus/demo2",
"pathhide": true
}
]
}
- whitelists: 白名单规则,给下面 policy 做接口访问规则配置,单独存在无意义
- name: 白名单名称
- process: 允许访问接口的程序列表
- description: 该条规则的描述,无实际意义
- policy: 若需要访问控制,则应在此进行配置
- path: object path,指定哪个 path 要进行配置
- pathhide: 隐藏该 path,但可调用。可选,默认 false
- permission: 开启权限。可选,默认 false。注意该功能在 V20 上不可用,V23 可正常使用,原因是 Qt 的 DBus 实现有问题。
- subpath: 子 path 也应用该权限(针对动态生成的子路径)。可选,默认 false
- whitelist: 开启权限后,调用上方的白名单规则
- interfaces: path->interfaces->methods,权限层级,未指定的下级继承上级的权限配置,指定了的覆盖上级配置
- interface: 指定哪个 interface 要进行配置
- permission: 不填则继承上级 PATH 的配置
- whitelist: 开启权限后,调用上方的白名单规则
- methods: 指定方法配置
- method: 指定哪个方法配置
- whitelist: 指定白名单
- properties: 指定属性配置
- property: 指定哪个属性配置
- permission: 指定不进行权限配置,任何人可以访问。
在原来的配置基础上,加上上面的配置即可加上权限管控功能(配置中的省略号表示之前的配置)!
若只需要隐藏 path,只需要配置 pathhide 字段即可,其他字段均不需要配置!
将 .so 和 .json 文件放到指定位置后,执行命令:
-
system
sudo systemctl restart deepin-service-manager.service
-
session
systemctl --user restart deepin-service-manager.service
重启服务后,即可通过 DBus 命令行或 d-feet 工具查看 json 中的 DBus 服务已被启动,服务名即 json 中的name
字段配置的内容。
在org.deepin.ServiceManager1
服务中:
/org/deepin/ServiceManager1
路径下可查看当前服务中已启动的所有分组进程/org/deepin/Group1/<group name>
路径下可查看当前分组中加载的所有插件
当startType
为Resident
时,插件会以常驻的方式进行启动,并且开机自启。
当startType
为OnDemand
时,插件需要依赖DBus
服务拉起,所以需要安装一个 DBus service 文件
该文件可以自己写,也可以自定义函数辅助生成:
function (install_dbus_service arg)
if (${ARGC} EQUAL 1)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${ARGV0}.service CONTENT "[D-BUS Service]\nName=${ARGV0}\nExec=/usr/bin/deepin-service-manager -n ${ARGV0}\nSystemdService=deepin-service-plugin@${ARGV0}.service")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${ARGV0}.service DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/dbus-1/services)
endif()
if (${ARGC} EQUAL 2)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${ARGV0}.service CONTENT "[D-BUS Service]\nName=${ARGV0}\nExec=/usr/bin/deepin-service-manager -n ${ARGV0}\nUser=root\nSystemdService=deepin-service-plugin@${ARGV0}.service")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${ARGV0}.service DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/dbus-1/system-services)
endif()
endfunction ()
install_dbus_service(org.deepin.ServiceName)
若是 system 级服务,还需要加一个参数:
install_dbus_service(org.deepin.ServiceName root)
若是手写一个 DBus service 文件,以下是一个参考例子:
[D-BUS Service]
Name=org.deepin.service.demo
Exec=/usr/bin/deepin-service-manager -n org.deepin.service.demo
User=root
SystemdService=deepin-service-plugin@org.deepin.service.demo.service
若需要注册 system 级别 DBus,还需要安装 conf 文件让 DBus 服务能够有权限进行注册,以下是一个例子:
<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Only root can own the service -->
<policy user="root">
<allow own="org.deepin.ServiceManager1"/>
<allow send_destination="org.deepin.ServiceManager1"/>
</policy>
<!-- Allow anyone to invoke methods on the interfaces -->
<policy context="default">
<allow send_destination="org.deepin.ServiceManager1"/>
</policy>
</busconfig>
在该服务中,分为主服务与分组服务,主服务启动,会根据配置文件,自动启动分组服务,举个例子:
现有一个插件,json 配置中,group
字段配置为app
,那么该插件就属于app
组,主服务启动时会自动按组名启动插件服务,插件服务的名称为:
deepin-service-group@app.service
所以在调试时,只需启动插件服务即可:
sudo systemctl restart deepin-service-group@app.service
对于非常驻插件,可以单独进程启动:
sudo systemctl restart deepin-service-plugin@org.deepin.service.demo.service
若是常驻应用,可按自己的分组查看:
sudo journalctl -x -e -u deepin-service-group@app.service
若是临时应用,可按服务名称查看:
sudo journalctl -x -e -u deepin-service-plugin@org.deepin.service.demo.service
demo 请参考这里
- 2023/03/17:
- 添加插件和独立应用的区别
- 2023/03/16:
- 支持插件以临时进程进行启动
- 服务名称改名:deepin-service-plugin -> deepin-service-group
- 优化注意事项,建议详细查看注意事项
- 新增日志查看方式
- 优化配置代码,字段说明提取到外面,方便复制
- 2023/02/22:
- 新增权限管控流程和独立应用开发流程
- 新增注意事项,调试技巧可参考注意事项
- 新增附件列表,实现了常见场景的 demo
- 优化整体页面说明,分为插件开发、独立应用开发以及如何给插件或应用加上权限管控
- 2023/02/08:
- 新增依赖配置,配置依赖服务后,在依赖未启动时不会启动本服务
- 新增延时启动,可配置本服务延时启动
- 2023/02/06:
- 重命名入口函数 DSMRegisterObject->DSMRegister;
- 新增卸载函数,用于释放内存:DSMUnRegister;
- json 配置路径更新:去掉了路径中的
qt-service
和sd-service
,转而使用 json 文件中的pluginType
来匹配。