diff --git a/debian/control b/debian/control index 56f79c01c..9e303b9d2 100644 --- a/debian/control +++ b/debian/control @@ -43,6 +43,7 @@ Description: Deepin cooperation backend Package: deepin-data-transfer Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, dde-cooperation-daemon +Recommends: deepin-security-loader Description: deepin data transfer frontend Deepin Desktop Environment (DDE) - data transfer frontend. diff --git a/debian/deepin-data-transfer.install b/debian/deepin-data-transfer.install index 1f7bb88de..462d99825 100644 --- a/debian/deepin-data-transfer.install +++ b/debian/deepin-data-transfer.install @@ -1,4 +1,5 @@ usr/bin/deepin-data-transfer +usr/libexec/deepin/deepin-data-transfer usr/lib/*/dde-cooperation/plugins/data-transfer/*.so* usr/share/deepin-data-transfer/translations/*.qm usr/share/icons/hicolor/scalable/apps/deepin-data-transfer.svg diff --git a/src/apps/data-transfer/CMakeLists.txt b/src/apps/data-transfer/CMakeLists.txt index 3730bb9bf..9d56bcda0 100644 --- a/src/apps/data-transfer/CMakeLists.txt +++ b/src/apps/data-transfer/CMakeLists.txt @@ -21,7 +21,7 @@ endif() if (WIN32) set(qt_required_components Core Gui Network Widgets Sql WinExtras) else() - set(qt_required_components Core Gui Network Widgets) + set(qt_required_components Core Gui Network Widgets DBus) endif() find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS ${qt_required_components}) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${qt_required_components}) @@ -76,11 +76,19 @@ target_link_libraries(${PROJ_NAME} if (CMAKE_SYSTEM MATCHES "Linux") target_link_libraries(${PROJ_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::DBus ${DtkWidget_LIBRARIES} ) endif() -install(TARGETS ${PROJ_NAME} DESTINATION bin) +if (WIN32) + install(TARGETS ${PROJ_NAME} DESTINATION bin) +else() + # 真实二进制安装到 libexec,/usr/bin 由启动脚本占用(见下), + # 以便经 deepin-security-loader 附加 deepin-daemon 组启动 + install(TARGETS ${PROJ_NAME} DESTINATION libexec/deepin) + install(PROGRAMS res/linux/deepin-data-transfer.sh DESTINATION bin RENAME deepin-data-transfer) +endif() # translation files TRANSLATION_GENERATE(QM_FILES ${CMAKE_SOURCE_DIR}/translations/${PROJ_NAME}) diff --git a/src/apps/data-transfer/main.cpp b/src/apps/data-transfer/main.cpp index 4d598a7fd..e7aa4a375 100644 --- a/src/apps/data-transfer/main.cpp +++ b/src/apps/data-transfer/main.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,7 +10,15 @@ #include #include +#include #include +#include +#include +#include +#include +#include + +#include static constexpr char kPluginInterface[] { "org.deepin.plugin.datatransfer" }; static constexpr char kPluginCore[] { "data-transfer-core" }; @@ -20,6 +28,91 @@ static constexpr char kPluginCore[] { "data-transfer-core" }; #define LIB_FILE_NAME(lib_name) QString("lib%1.so").arg(#lib_name) #endif +#ifdef linux +// deepin-security-loader 启动协议(与 deepin-home-appstore-client 行为一致): +// 进程经 loader 附加 deepin-daemon 组启动时,loader 会附加 --fd1/--fd2 命令行参数, +// 分别传入一对管道:子进程把自身 system bus 唯一连接名和需要免鉴权的服务列表 +// 以 JSON 写入 fd1,loader 收到后以组身份代为调用各服务的 SetAllowCaller 白名单 +// 注册,之后 InstallPackage 等接口免 polkit 鉴权。 +// 子进程若不回报,loader 会超时退出并结束子进程(表现为启动闪退), +// 因此经 loader 启动时必须尽早完成回报;直接启动(无 --fd1)则跳过。 +static void initSecurityLoaderReport() +{ + const QStringList args = QCoreApplication::arguments(); + int fd1 = -1, fd2 = -1; + for (int i = 1; i + 1 < args.count(); ++i) { + if (args.at(i) != QLatin1String("--fd1") && args.at(i) != QLatin1String("--fd2")) + continue; + // toInt() 解析失败会返回 0(即 stdin),必须校验,避免误把标准输入当作管道 fd + bool ok = false; + const int fd = args.at(i + 1).toInt(&ok); + if (!ok) + continue; + if (args.at(i) == QLatin1String("--fd1")) + fd1 = fd; + else + fd2 = fd; + } + if (fd1 < 0) + return; // 未经 security-loader 启动,无需回报 + + // 管道 fd 保持打开(loader 侧不依赖 EOF 读取,与参照实现 appstore-client 一致), + // 但设置 FD_CLOEXEC,避免回报管道泄漏给后续 QProcess 派生的子进程 + if (fcntl(fd1, F_SETFD, FD_CLOEXEC) < 0) + qWarning() << "failed to set FD_CLOEXEC on fd" << fd1; + if (fd2 >= 0 && fcntl(fd2, F_SETFD, FD_CLOEXEC) < 0) + qWarning() << "failed to set FD_CLOEXEC on fd" << fd2; + + const QString uniqueName = QDBusConnection::systemBus().baseService(); + if (uniqueName.isEmpty()) { + qWarning() << "system bus not connected, skip allow-caller report"; + return; + } + + QJsonObject dest; + dest.insert(QStringLiteral("DbusName"), QStringLiteral("com.deepin.lastore")); + dest.insert(QStringLiteral("DbusPath"), QStringLiteral("/com/deepin/lastore")); + dest.insert(QStringLiteral("DbusInterface"), QStringLiteral("com.deepin.lastore.Manager")); + QJsonObject msg; + msg.insert(QStringLiteral("UniqueName"), uniqueName); + msg.insert(QStringLiteral("DestList"), QJsonArray { dest }); + + QFile reportPipe; + if (!reportPipe.open(fd1, QIODevice::WriteOnly, QFileDevice::DontCloseHandle)) { + qWarning() << "failed to open security-loader report pipe:" << fd1; + return; + } + reportPipe.write(QJsonDocument(msg).toJson(QJsonDocument::Compact)); + reportPipe.flush(); + reportPipe.close(); + qInfo() << "allow-caller report sent:" << uniqueName; + + // fd2 可读回 {"Result":...,"ErrorMsg":...}。管道为非阻塞模式,loader 处理需要 + // 一次 dbus 往返,轮询等待一小段时间用于日志,失败不影响主流程; + // 最多等 500ms——该调用位于单例检查之前,不应拖慢启动与窗口激活 + if (fd2 >= 0) { + QFile resultPipe; + if (resultPipe.open(fd2, QIODevice::ReadOnly, QFileDevice::DontCloseHandle)) { + QByteArray resultData; + for (int i = 0; i < 10 && resultData.isEmpty(); ++i) { + resultData = resultPipe.readAll(); + if (resultData.isEmpty()) + QThread::msleep(50); + } + resultPipe.close(); + if (resultData.isEmpty()) { + qWarning() << "no allow-caller result from security-loader within 500ms"; + } else { + const QJsonObject result = QJsonDocument::fromJson(resultData).object(); + qInfo() << "allow-caller register result:" + << result.value(QLatin1String("Result")).toBool() + << result.value(QLatin1String("ErrorMsg")).toString(); + } + } + } +} +#endif + static bool loadPlugins() { QStringList pluginsDirs; @@ -92,6 +185,13 @@ int main(int argc, char *argv[]) #endif +#ifdef linux + // 必须先于任何可能退出的分支(含单例失败):经 security-loader 启动时若不回报 + // 连接名,loader 会超时并结束子进程,表现为启动闪退;该回报同时完成 lastore + // 免鉴权白名单注册,需早于插件里 SettingHelper 的第一次 InstallPackage + initSecurityLoaderReport(); +#endif + bool canSetSingle = app.setSingleInstance(app.applicationName()); if (!canSetSingle) { qInfo() << "single application is already running."; diff --git a/src/apps/data-transfer/res/linux/deepin-data-transfer.sh b/src/apps/data-transfer/res/linux/deepin-data-transfer.sh new file mode 100755 index 000000000..d10a9f0c8 --- /dev/null +++ b/src/apps/data-transfer/res/linux/deepin-data-transfer.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# Deepin Data Transfer launcher +# 参照 deepin-home-appstore-client:满足条件时经 deepin-security-loader +# 附加 deepin-daemon 组启动,使 lastore 免鉴权接口调用可行; +# 否则直接启动,鉴权回退为系统 polkit 弹窗(等同历史行为)。 + +REAL_BIN=/usr/libexec/deepin/deepin-data-transfer + +# Check if /usr/bin/deepin-security-loader is executable and dbus version meets +# requirement(用 -x 而非 -f:exec 失败时 bash 会直接退出 127,无法走 || 回退, +# 须在 exec 前排除不可执行的 loader) +if [ -x "/usr/bin/deepin-security-loader" ]; then + # Check dbus version + if DBUS_VERSION=$(dpkg-query -W -f='${Version}' dbus 2>/dev/null); then + # Compare dbus version with 1.12.20.17-deepin1 + if dpkg --compare-versions "$DBUS_VERSION" ge "1.12.20.17-deepin1"; then + # Use security loader with deepin-daemon group + exec /usr/bin/deepin-security-loader --group deepin-daemon -- "$REAL_BIN" "$@" + fi + fi +fi + +# Fallback to direct launch +exec "$REAL_BIN" "$@"