以下代码是一个 session 服务和 systemd 服务 demo :
systemd DBus
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusError>
#include <QDebug>
class TestObject : public QObject
{
Q_OBJECT
public:
TestObject() {
registerSystemDBus();
callSession()
}
void registerSystemDBus() {
QDBusConnection systemBus = QDBusConnection::systemBus();
if (!systemBus.isConnected()) {
qDebug() << "Failed to connect to system bus:" << systemBus.lastError().message();
return;
}
if (!systemBus.registerService("com.example.TestSystemdService")) {
qDebug() << "Failed to register service:" << systemBus.lastError().message();
return;
}
if (!systemBus.registerObject("/com/example/TestObject",
this,
QDBusConnection::ExportAllSlots)) {
qDebug() << "Failed to register object:" << systemBus.lastError().message();
return;
}
}
void callSession()
{
QDBusInterface interface("com.example.TestService",
"/com/example/TestObject",
"local.session.MainWindow",
QDBusConnection::sessionBus());
if (!interface.isValid()) {
qDebug() << "Failed to create interface:" << interface.lastError().message();
return;
}
QDBusMessage reply = interface.call("testMethod");
if (reply.type() == QDBusMessage::ErrorMessage) {
qDebug() << "Call failed:" << reply.errorMessage();
} else {
qDebug() << "Call succeeded.";
}
}
public slots:
void testMethod() {
qDebug() << "testMethod called";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TestObject testObject;
QString path = QProcessEnvironment::systemEnvironment().value("DBUS_SESSION_BUS_ADDRESS");
qDebug() << "path is " << path; // path is ""
return a.exec();
}
#include "main.moc"
cmakelist
cmake_minimum_required(VERSION 3.14)
project(systemd LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
find_package(Qt5DBus)
find_package(PkgConfig REQUIRED)
pkg_check_modules(QGSettings REQUIRED gsettings-qt)
add_executable(systemd
main.cpp
)
target_link_libraries(systemd Qt${QT_VERSION_MAJOR}::Core
Qt5::DBus
${QGSettings_LIBRARIES})
install(TARGETS systemd
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
session DBus
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QDebug>
#include <QDBusConnection>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString path = QProcessEnvironment::systemEnvironment().value("DBUS_SESSION_BUS_ADDRESS");
qDebug() << "path is " << path; // path is "unix:path=/run/user/1000/bus"
registerSessionDBus();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::registerSessionDBus()
{
QDBusConnection::sessionBus().registerService("com.example.TestService");
if (!QDBusConnection::sessionBus().registerObject("/com/example/TestObject",
this, QDBusConnection::ExportAllSlots)) {
qDebug() << "fail";
}
}
void MainWindow::testMethod()
{
qDebug() << "test";
}
cmakelist
cmake_minimum_required(VERSION 3.5)
project(session VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
find_package(Qt5DBus)
find_package(PkgConfig REQUIRED)
pkg_check_modules(QGSettings REQUIRED gsettings-qt)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(session
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET session APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(session SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(session
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(session PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt5::DBus ${QGSettings_LIBRARIES})
set_target_properties(session PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
install(TARGETS session
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(session)
endif()
当在 Systemed DBus 服务中,调用 session DBus 服务的接口,会发现即使 session 服务在运行,但依然会报如下错误:
Failed to create interface: "Not connected to D-Bus server"
此时当我们在 demo 中增加以下日志输出,会发现在 root 下,DBUS_SESSION_BUS_ADDRESS 输出为空。
QString path = QProcessEnvironment::systemEnvironment().value("DBUS_SESSION_BUS_ADDRESS");
qDebug() << "session service print path is " << path; // "unix:path=/run/user/1000/bus"
qDebug() << "system service print path is " << path; // ""
而 DBUS_SESSION_BUS_ADDRESS 为空,即为导致报错的原因,在解释 DBUS_SESSION_BUS_ADDRESS 为空会导致报错的原因之前,先了解几个概念:
dbus-daemon: 是 D-Bus 守护进程的实际实现,用于管理和协调消息传递,作为 D-Bus 的消息中转枢纽,可分成 system 和 session 两种。
System Bus:这个总线用于系统范围内的通信,通常涉及到系统服务和守护进程。
Session Bus:这个总线用于用户会话范围内的通信,通常涉及到用户应用程序。
DBUS_SESSION_BUS_ADDRESS:环境变量,用于存储会话总线的地址。
dbus-daemon官方文档参数介绍
当用户登录时,会启动一个 dbus-daemon --session 进程,这个进程会创建一个会话总线并生成一个唯一的地址。这个地址会存储在 DBUS_SESSION_BUS_ADDRESS 环境变量中。当某个应用程序需要和其他应用程序通信时,它会读取 DBUS_SESSION_BUS_ADDRESS 环境变量来找到会话总线并进行通信。如果这个环境变量为空或者没有正确设置,服务将无法连接到会话总线,从而导致无法找到目标服务文件。
参考文献
D-Bus Specification
dbus-daemon
systemd/User - ArchWiki
解决方案供参考
如果你给 root 用户导入了普通用户的 DBUS_SESSION_BUS_ADDRESS 环境变量,但仍然不能访问会话总线(session bus)服务,可能有以下几个原因:
1. 权限问题
会话总线的 Unix 套接字通常设置为只有特定用户才能访问。即使你将 DBUS_SESSION_BUS_ADDRESS 设置为指向普通用户的会话总线地址,root 用户可能没有访问该套接字的权限。
2. 会话总线的安全策略
D-Bus 有一套安全策略,用于限制哪些用户和进程可以访问哪些服务。即使 root 用户可以访问会话总线地址,也可能被会话总线的安全策略阻止访问特定服务。
3. 会话总线的用户隔离
会话总线通常设计为与用户会话隔离。每个用户会话有自己的会话总线,且这些总线是相互隔离的,防止不同用户会话之间的干扰。
解决方案
方法一:修改会话总线套接字权限
你可以尝试修改会话总线套接字的权限,以允许 root 用户访问。这种方法比较危险,需要谨慎操作:
查看套接字的权限:
ls -l /run/user/1000/bus
修改套接字的权限(不推荐):
chmod 777 /run/user/1000/bus
这种方法会将套接字的权限设置为所有用户可读写,但可能会带来安全风险。
方法二:使用 dbus-launch
可以使用 dbus-launch 在 root 会话中启动一个新的会话总线:
启动一个新的会话总线:
sudo dbus-launch --sh-syntax
这会输出一组新的环境变量,包括 DBUS_SESSION_BUS_ADDRESS。
导出这些环境变量:
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
运行需要访问会话总线的命令:
your_command_here
方法三:通过代理或中继访问
可以通过代理或中继的方式在 root 环境中访问用户会话总线服务。这种方法需要设置一些额外的工具和配置。
假设你需要让 root 用户访问普通用户的 D-Bus 服务,可以尝试以下步骤:
启动新会话总线
(如果需要 root 用户有自己的会话总线):sudo dbus-launch --sh-syntax
导出新的会话总线环境变量
将命令输出的环境变量导出到当前环境。
确保会话总线地址正确设置
确保 DBUS_SESSION_BUS_ADDRESS 环境变量正确指向普通用户的会话总线地址。
测试访问服务
尝试从 root 用户运行需要访问会话总线服务的命令,确认是否能够成功访问。
# 获取普通用户的 DBUS_SESSION_BUS_ADDRESS
USER_DBUS_SESSION_BUS_ADDRESS=$(sudo -u your_username echo $DBUS_SESSION_BUS_ADDRESS)
# 切换到 root 用户并导出会话总线地址
sudo -i <<EOF
export DBUS_SESSION_BUS_ADDRESS=$USER_DBUS_SESSION_BUS_ADDRESS
your_command_here
EOF
总结
即使将 DBUS_SESSION_BUS_ADDRESS 环境变量正确设置为普通用户的会话总线地址,root 用户可能仍然无法访问会话总线服务,原因包括权限问题、安全策略和用户会话隔离等。可以尝试修改套接字权限、使用 dbus-launch 启动新的会话总线或通过代理访问来解决这个问题,但每种方法都有其限制和风险。