openharmony北向开发之在系统服务中使用分布式文件系统demo

系统服务分布式文件(HMDFS)接入指南

版本 :2026-07

适用范围 :OpenHarmony 系统服务(sa_main + init cfg)跨设备共享本机文件

目标读者 :已有可运行系统服务、需要接入 HMDFS 的开发者

自包含原则 :第 0~10 章说明前置条件、原理与接入步骤;第 11 章提供全部可编译源码,仅阅读本文即可完成 Demo 开发


0. 前置条件

在动手写代码之前,请逐项确认以下环境。任一不满足,Demo 大概率会在 P2P、share 注册或读文件阶段失败。

0.0 命令约定(必读)

下文命令分两类,请勿混用:

写法 执行位置 说明
hdc shell <命令> PC 端 执行 通过 HDC 转发到设备
hdc shell 前缀 设备 shell 内执行 hdc shell 进入设备后使用

常见错误hdc shell cat /proc/$(pidof helloworld_service)/status 会在 PC 上 展开 $(pidof ...),得到 /proc/0/status 报错。正确写法:

bash 复制代码
hdc shell "cat /proc/\$(pidof helloworld_service)/status | grep Groups"

hdc shell mount | grep hmdfs 中管道右侧 grep 在 PC 端执行,经实测可用。

0.1 服务与工程

条件 说明
HelloWorld SA 可运行 已有一个能被 init 拉起、通过 sa_main + profile 正常运行的系统服务(下文称 helloworld_service
两台设备 两台运行同一套系统镜像的设备(或等效双机环境),用于验证跨设备传文件
dfs_service 已集成 系统镜像包含分布式文件守护进程(dfs_service),且开机自启

0.2 软总线组网与设备发现(必须)

分布式文件的 P2P 链路和 networkId 获取都依赖软总线。两台设备必须已完成组网,且能互相发现。

软总线可用的前置条件(缺一不可):

条件 说明
系统已烧录 SN 号 设备须具备有效序列号(SN),未烧录 SN 的设备无法正常参与分布式组网
系统已激活 设备须完成系统激活流程;未激活状态下软总线能力不可用或组网异常

上述两项是软总线组网的前提。SN 未烧录或系统未激活时,即使 softbus_server 进程存在,也可能无法获取有效 networkId、无法完成设备发现与 P2P 建链。

bash 复制代码
# 1. 确认 softbus 服务在运行(grep 在 PC 端过滤 hdc 输出)
hdc shell ps -ef | grep softbus

# 2. 确认设备已登录(HMDFS 路径默认 userId=100)
hdc shell ls /data/service/el2/100/hmdfs/

# 3. 获取本机 64 字节 networkId
#    在 Demo 代码中调用 GetLocalNetworkId() 并 hilog 打印(推荐、跨镜像通用)
#    两台设备日志中均应出现非空 64 字符 networkId

判断标准 :两台设备均能获取到非空的 64 字节 networkId,且处于同一超级设备/分布式组网中。若组网未完成,OpenP2PConnection 和远程 share 读取必定失败。

0.3 HMDFS 已挂载

bash 复制代码
hdc shell mount | grep hmdfs
# 期望看到类似:/mnt/hmdfs/100/account/...

hdc shell ls /mnt/hmdfs/100/account/merge_view/services/
# 目录存在即可,share 子目录由 Demo 代码自动创建

若 HMDFS 未挂载,检查用户是否已登录、dfs_service 是否正常运行,必要时 reboot 后重试。

0.4 SELinux:调试阶段建议宽容模式

首次调通 Demo 时,强烈建议 将 SELinux 设为宽容模式,避免 ioctlsetxattr、写 HMDFS 路径等环节被 AVC 拒绝。

查看当前模式(PC 端执行):

bash 复制代码
hdc shell getenforce

输出 Permissive0 表示宽容模式;Enforcing 表示强制模式。

切换为宽容模式 (写入配置后 必须 reboot 开发板):

bash 复制代码
hdc shell 'mount -o remount,rw / && echo "SELINUX=permissive" > /etc/selinux/config'
# reboot 后再次执行:
hdc shell getenforce

恢复强制模式(同样需 reboot,调试结束后执行):

bash 复制代码
hdc shell 'mount -o remount,rw / && echo "SELINUX=enforcing" > /etc/selinux/config'
场景 处理方式
调试阶段,绕过 AVC 拒绝 按上文写入 SELINUX=permissive 并 reboot
服务无法启动(pidof 无进程) 检查 cfg 中 secon 是否在 sepolicy 有定义;未定义时 即使宽容模式也无法启动 ,需先配 sepolicy 或调试阶段临时去掉 secon 字段
正式产品 在 sepolicy 中为 helloworld_service 域添加 HMDFS 相关规则,参见 doc/系统服务init配置与SELinux策略一致性报告.md

0.5 init cfg 与构建校验

条件 说明
giddfs_share 否则 share 目录 mkdir / ioctl 报 Permission denied
分布式权限 DISTRIBUTED_DATASYNCDISTRIBUTED_SOFTBUS_CENTER
sandbox: 0 系统服务需直接访问 /mnt/hmdfs//data/service/el2/
uidsystem 产品的 high_privilege_process_list.json 需登记 helloworld_service,否则构建 process_field_validate 阶段失败
修改 cfg 后 必须 reboot 或 stop/start helloworld_service,进程 Groups 才会更新
bash 复制代码
# 验证进程组含 dfs_share(注意整条命令加引号,见 0.0 节)
hdc shell "cat /proc/\$(pidof helloworld_service)/status | grep Groups"

0.6 前置条件检查清单

调 Demo 前快速过一遍:

复制代码
[ ] 两台设备均已烧录 SN 号且系统已激活
[ ] 两台设备软总线组网成功,能互相发现
[ ] 两台设备均能获取 64 字节 networkId
[ ] mount | grep hmdfs 有输出
[ ] getenforce 为 Permissive(或已配好 sepolicy)
[ ] helloworld_service 进程存在且 Groups 含 dfs_share
[ ] /data/helloworld/ 目录存在(cfg jobs 创建)

全部打勾后,再按第 4 章改造 SA、第 11 章复制源码。


1. 场景:两台设备传一份文本

用最小场景说明「发布路径」和「消费路径」。

复制代码
设备 B(发布方 helloworld_service)          设备 A(消费方 helloworld_service)
─────────────────────────────────          ─────────────────────────────────
1. 业务写文件
   /data/helloworld/hello.txt
2. 调用 PublishDistributedFile()
   → 复制到 HMDFS backend
   → ioctl 注册 share
   → 返回 distributedRemoteSharePath
3. 把路径传给设备 A
   (IPC / 自定义 RPC / 调试日志,任选)
                                             4. 收到 remotePath
                                             5. 调用 ReadDistributedFile(remotePath)
                                                → P2P 建链
                                                → 复制到本地缓存
                                             6. 用 ifstream 读缓存文件内容

约定

角色 输入 输出
发布方 本机绝对路径,如 /data/helloworld/hello.txt distributedRemoteSharePath(给对端)
消费方 发布方返回的 distributedRemoteSharePath localReadablePath(本机可读路径)

消费方必须 使用发布方返回的 share 路径,不要把 HMDFS remote view 路径直接交给 ifstream / curl


2. HMDFS 做了什么

系统服务跑在非应用沙箱 ,不能走应用侧的 distributedfiles/.share。需要走 HMDFS 正式链路,共四步:

步骤 动作 对应接口
L1 落盘 把本机文件复制到 HMDFS backend HmdfsFilePublisher::Publish
L2 共享 ioctl 注册 share,对端可见 .share 路径 HmdfsShareHelper::RegisterShareFile
L3 建链 与对端建立 HMDFS P2P OpenP2PConnection(dfs daemon)
L4 消费 解析远程路径、建链、落本地缓存后读取 DistributedPathResolver::Resolve

消费方 SA dfs_service HMDFS 发布方 SA 消费方 SA dfs_service HMDFS 发布方 SA #mermaid-svg-TOCK12KPOUiS8dhO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TOCK12KPOUiS8dhO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TOCK12KPOUiS8dhO .error-icon{fill:#552222;}#mermaid-svg-TOCK12KPOUiS8dhO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TOCK12KPOUiS8dhO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TOCK12KPOUiS8dhO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TOCK12KPOUiS8dhO .marker.cross{stroke:#333333;}#mermaid-svg-TOCK12KPOUiS8dhO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TOCK12KPOUiS8dhO p{margin:0;}#mermaid-svg-TOCK12KPOUiS8dhO .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TOCK12KPOUiS8dhO text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TOCK12KPOUiS8dhO .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TOCK12KPOUiS8dhO .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-TOCK12KPOUiS8dhO .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TOCK12KPOUiS8dhO .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TOCK12KPOUiS8dhO #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TOCK12KPOUiS8dhO .sequenceNumber{fill:white;}#mermaid-svg-TOCK12KPOUiS8dhO #sequencenumber{fill:#333;}#mermaid-svg-TOCK12KPOUiS8dhO #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TOCK12KPOUiS8dhO .messageText{fill:#333;stroke:none;}#mermaid-svg-TOCK12KPOUiS8dhO .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TOCK12KPOUiS8dhO .labelText,#mermaid-svg-TOCK12KPOUiS8dhO .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TOCK12KPOUiS8dhO .loopText,#mermaid-svg-TOCK12KPOUiS8dhO .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TOCK12KPOUiS8dhO .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TOCK12KPOUiS8dhO .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TOCK12KPOUiS8dhO .noteText,#mermaid-svg-TOCK12KPOUiS8dhO .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TOCK12KPOUiS8dhO .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TOCK12KPOUiS8dhO .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TOCK12KPOUiS8dhO .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TOCK12KPOUiS8dhO .actorPopupMenu{position:absolute;}#mermaid-svg-TOCK12KPOUiS8dhO .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TOCK12KPOUiS8dhO .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TOCK12KPOUiS8dhO .actor-man circle,#mermaid-svg-TOCK12KPOUiS8dhO line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TOCK12KPOUiS8dhO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 复制文件到 backendioctl SET_SHARE_PATHOpenP2PConnection(对端 networkId)distributedRemoteSharePathOpenP2PConnection(发布方 networkId)读远程 share 路径复制到本地缓存


3. 路径体系

userId = 100、服务名 helloworld_service、文件名 hello.txt、发布端 networkId {publisherNetworkId} 为例:

名称 路径
业务本机路径 /data/helloworld/hello.txt
HMDFS Backend(发布端真实文件) /data/service/el2/100/hmdfs/account/files/share/hello.txt
HMDFS Local View /mnt/hmdfs/100/account/device_view/local/files/share/hello.txt
Share 本地视图 /mnt/hmdfs/100/account/merge_view/services/helloworld_service/.share/hello.txt
Share 远程视图(给对端) /mnt/hmdfs/100/account/device_view/{publisherNetworkId}/services/helloworld_service/.share/hello.txt

shareServiceName 必须与 init cfg 里的服务名一致(此处为 helloworld_service),否则 share 目录权限对不上。


4. 改造 HelloWorld SA

假设你已有如下骨架(仅示意,按你工程实际为准):

复制代码
your_sa/
├── etc/
│   └── helloworld_service.cfg      # init 配置
├── profile/
│   └── helloworld_service.json     # SA profile
├── services/
│   └── helloworld_service.cpp      # 业务实现
├── hmdfs/                          # ← 新增:分布式文件工具
│   ├── hmdfs_file_publisher.{h,cpp}
│   ├── hmdfs_share_helper.{h,cpp}
│   └── distributed_path_resolver.{h,cpp}
└── BUILD.gn

4.1 添加 HMDFS 工具代码

第 11 章「源码」 中的 hmdfs/ 目录 6 个文件复制到你的工程。若你的服务名不是 helloworld_service,只需修改以下常量(均在源码中标注):

常量 默认值 说明
DEFAULT_SERVICE_NAME helloworld_service 与 init cfg 服务名一致
kSoftbusPkgCandidates 见 11.6 节 获取本机 networkId,含系统 dfs 兜底包名
LOCAL_CACHE_DIR /data/service/el1/public/helloworld_cache 消费方本地缓存目录

完整可编译源码见 第 11 章,无需从其他模块复制。

4.2 BUILD.gn

gn 复制代码
ohos_static_library("hmdfs_helper") {
  use_exceptions = true
  sources = [
    "hmdfs/hmdfs_file_publisher.cpp",
    "hmdfs/hmdfs_share_helper.cpp",
    "hmdfs/distributed_path_resolver.cpp",
  ]
  include_dirs = [ "hmdfs" ]
  external_deps = [
    "dfs_service:distributed_file_daemon_kit_inner",
    "hilog:libhilog",
    "dsoftbus:softbus_client",
  ]
}

ohos_shared_library("libhelloworld_service") {
  sources = [ "services/helloworld_service.cpp" ]
  deps = [ ":hmdfs_helper" ]
  include_dirs = [ "hmdfs" ]
  # ... 你原有的 SA 依赖
}

4.3 修改 init cfg

helloworld_service.cfgservices 段补充以下字段(完整示例见第 6 节):

字段 必须 原因
"gid": [..., "dfs_share"] share 目录属主为 dfs_share 组,否则 mkdir / ioctl 报 Permission denied
"ohos.permission.DISTRIBUTED_DATASYNC" 调用 OpenP2PConnection
"ohos.permission.DISTRIBUTED_SOFTBUS_CENTER" 获取本机 64 字节 networkId
"caps": ["DAC_READ_SEARCH", "DAC_OVERRIDE", "CHOWN", "FOWNER"] 建议 写 HMDFS backend、遍历挂载树
"sandbox": 0 需直接访问 /mnt/hmdfs//data/service/el2/
"secon": "u:r:helloworld_service:s0" 与 sepolicy 域一致

修改 cfg 后必须 重启服务或 reboot ,进程 Groups 才会包含 dfs_share

验证(在 设备 shell 内执行,或 PC 端用引号包裹,见 0.0 节):

bash 复制代码
cat /proc/$(pidof helloworld_service)/status | grep Groups
# 应包含 dfs_share 对应 gid(如 3822)

5. 业务代码

5.1 发布:把本机文件变成对端可访问路径

helloworld_service 中调用第 11 章提供的业务接口(或直接 #include "helloworld_distributed_file.h"):

cpp 复制代码
#include "helloworld_distributed_file.h"

bool YourSa::PublishDistributedFile(
    const std::string& localPath,
    const std::string& peerNetworkId,
    std::string& remoteSharePath,
    std::string& errMsg)
{
    return OHOS::DistributedFile::PublishDistributedFile(localPath, peerNetworkId, remoteSharePath, errMsg);
}

或直接调用:

cpp 复制代码
#include "hmdfs_file_publisher.h"
#include "hmdfs_share_helper.h"

bool YourSa::PublishDistributedFile(
    const std::string& localPath,
    const std::string& peerNetworkId,   // 消费方 64 字节 networkId;空则 share 给全组网 "0"
    std::string& remoteSharePath,
    std::string& errMsg)
{
    HmdfsPublishOptions options;
    options.relativeSubdir = "share/";
    options.enableShareRegister = true;
    options.shareServiceName = "helloworld_service";
    options.targetNetworkId = peerNetworkId;

    HmdfsPublishResult result;
    if (!HmdfsFilePublisher::Publish(localPath, "100", result, errMsg, options)) {
        return false;
    }
    if (!result.shareRegistered) {
        errMsg = result.shareRegisterErr.empty() ? "share register failed" : result.shareRegisterErr;
        return false;
    }

    remoteSharePath = result.distributedRemoteSharePath;
    return !remoteSharePath.empty();
}

调用示例(设备 B)

cpp 复制代码
// 1. 准备文件
std::ofstream("/data/helloworld/hello.txt") << "hello from device B";

// 2. 发布
std::string remotePath;
std::string err;
if (!OHOS::DistributedFile::PublishDistributedFile(
        "/data/helloworld/hello.txt", peerNetworkIdOnDeviceA, remotePath, err)) {
    // 打日志 err
    return;
}

// 3. 把 remotePath 传给设备 A(调试阶段可直接 hilog 打印)
HILOG_INFO("Publish ok, remotePath=%{public}s", remotePath.c_str());

成功时 remotePath 形如:

复制代码
/mnt/hmdfs/100/account/device_view/{publisherNetworkId}/services/helloworld_service/.share/hello.txt

5.2 消费:根据远程路径读本机可读文件

cpp 复制代码
#include "helloworld_distributed_file.h"

bool YourSa::ReadDistributedFile(
    const std::string& remoteSharePath,
    std::string& content,
    std::string& errMsg)
{
    return OHOS::DistributedFile::ReadDistributedFile(remoteSharePath, content, errMsg);
}

或直接调用:

cpp 复制代码
#include "distributed_path_resolver.h"

bool YourSa::ReadDistributedFile(
    const std::string& remoteSharePath,
    std::string& content,
    std::string& errMsg)
{
    DistributedPathRequest request;
    request.path = remoteSharePath;

    DistributedPathResult result;
    if (!DistributedPathResolver::Resolve(request, result, errMsg)) {
        return false;
    }

    std::ifstream input(result.localReadablePath);
    if (!input.is_open()) {
        errMsg = "open local cache failed: " + result.localReadablePath;
        return false;
    }
    content.assign(std::istreambuf_iterator<char>(input), {});
    return true;
}

调用示例(设备 A)

cpp 复制代码
std::string content;
std::string err;
if (OHOS::DistributedFile::ReadDistributedFile(remotePathFromB, content, err)) {
    // 打日志 err
    return;
}
HILOG_INFO("Read ok, content=%{public}s", content.c_str());
// 期望输出: hello from device B

5.3 路径怎么从 B 传到 A

本文只要求你能把 distributedRemoteSharePath 字符串从发布方送到消费方,方式不限:

方式 适用
双机调试时手动复制路径 验证链路
已有 IPC / RPC 接口透传字符串 生产
软总线自定义消息 已有组网通道时

5.4 获取对端 networkId(调试)

发布前需要知道消费方(或发布方)的 64 字节 networkId,用于 P2P 预连接:

cpp 复制代码
// 发布方代码里:
std::string localId = OHOS::DistributedFile::GetLocalNetworkId();
if (localId.empty()) {
    // networkId 为空:检查 0.2 节软总线组网、0.3 节 HMDFS 挂载
}

targetNetworkId 必须是 64 字符 networkId,或 "0" 表示 share 给组网内所有设备。传空格会被清空,等效未指定。


6. helloworld_service.cfg 示例

json 复制代码
{
    "jobs": [{
        "name": "service:helloworld_service",
        "cmds": [
            "mkdir /data/helloworld 0750 system system",
            "mkdir /data/service/el1/public/helloworld_cache 0750 system system",
            "chown system system /data/helloworld",
            "chown system system /data/service/el1/public/helloworld_cache"
        ]
    }],
    "services": [{
        "name": "helloworld_service",
        "path": ["/system/bin/sa_main", "/system/profile/helloworld_service.json"],
        "uid": "system",
        "gid": ["system", "shell", "dfs_share"],
        "jobs": {
            "on-start": "service:helloworld_service"
        },
        "caps": ["DAC_READ_SEARCH", "DAC_OVERRIDE", "CHOWN", "FOWNER"],
        "secon": "u:r:helloworld_service:s0",
        "sandbox": 0,
        "permission": [
            "ohos.permission.DISTRIBUTED_DATASYNC",
            "ohos.permission.DISTRIBUTED_SOFTBUS_CENTER"
        ]
    }]
}

SELinux 域 helloworld_service 需在 sepolicy 中声明。调试阶段 可按第 0.4 节切换宽容模式;若服务无法启动,检查 secon 是否在 sepolicy 有定义。正式产品参见 系统服务init配置与SELinux策略一致性报告.md

消费方本地缓存目录默认为 /data/service/el1/public/helloworld_cache(在 distributed_path_resolver.cppDefaultCacheDir() 中修改)。


7. API 速查

HmdfsFilePublisher::Publish

cpp 复制代码
struct HmdfsPublishOptions {
    std::string relativeSubdir {"share/"};
    bool enableShareRegister {true};
    std::string shareServiceName {"helloworld_service"};
    std::string targetNetworkId {};
};

struct HmdfsPublishResult {
    std::string localPath;
    std::string hmdfsBackendPath;
    std::string distributedRemoteSharePath;  // ★ 给对端
    bool shareRegistered;
    std::string shareRegisterErr;
};

static bool Publish(const std::string& sourcePath, const std::string& userId,
    HmdfsPublishResult& result, std::string& errMsg,
    const HmdfsPublishOptions& options = {});

DistributedPathResolver::Resolve

cpp 复制代码
struct DistributedPathRequest {
    std::string path;    // 发布方返回的 distributedRemoteSharePath
    std::string userId {"100"};
};

struct DistributedPathResult {
    std::string localReadablePath;  // ★ 本机可读路径
    std::string networkId;
    bool copiedToCache;
};

static bool Resolve(const DistributedPathRequest& request,
    DistributedPathResult& result, std::string& errMsg);

远程 share 路径的处理顺序:先 P2P → 再 CopyToLocalCache → 再读本地文件。不能跳过缓存直接读 HMDFS 远程节点文件。


8. 端到端验证步骤

两台已组网设备,均部署含 HMDFS 能力的 helloworld_service。以下命令在 设备 shell 内 执行(PC 端可在每条前加 hdc shell,挂载检查也可用 hdc shell mount | grep hmdfs):

bash 复制代码
# 1. 确认 HMDFS 已挂载
mount | grep hmdfs

# 2. 设备 B:触发发布(通过你的 SA 调试接口或测试命令)
#    记录日志中的 distributedRemoteSharePath

# 3. 设备 B:确认 backend 落盘(发布成功后才有)
ls -la /data/service/el2/100/hmdfs/account/files/share/

# 4. 设备 B:确认 share 目录(注册成功后才有文件)
ls -la /mnt/hmdfs/100/account/merge_view/services/helloworld_service/.share/

# 5. 设备 A:用第 2 步的路径调用 ReadDistributedFile
#    确认读到 "hello from device B"

# 6. 确认进程组含 dfs_share
cat /proc/$(pidof helloworld_service)/status | grep Groups

推荐日志过滤(PC 端执行):

bash 复制代码
hdc shell hilog -b D -T HmdfsFile
hdc shell hilog -b D -T HmdfsShare
hdc shell hilog -b D -T DistributedPath

9. 故障排查

现象 可能原因 处理
GetLocalNetworkId() 返回空 软总线未组网、SN 未烧录、系统未激活或服务未获取设备信息 确认第 0.2 节(含 SN 烧录与系统激活);检查 kSoftbusPkgCandidates 兜底包名
distributedRemoteSharePath is empty 本机 networkId 获取失败 同上;发布前必须 networkId 非空
source file not readable 源路径不存在或无读权限 检查 /data/helloworld/ 及 cfg 中目录创建
create hmdfs parent dir failed 无写 backend 权限 / HMDFS 未挂载 检查 DAC_OVERRIDE、第 0.3 节 HMDFS 挂载
create local share dir failed 进程无 dfs_share gid 更新 cfg 并重启服务
OpenP2PConnection failed 无分布式权限、对端离线、未组网、SN/激活异常 检查第 0.2 节(含 SN 烧录与系统激活)组网与权限
share register failed / ioctl 失败 SELinux 拒绝或 HMDFS 未就绪 按第 0.4 节切换宽容模式并 reboot;确认 HMDFS 挂载完成
消费端 distributed file not accessible 未先发布、P2P 未建立、路径错误 必须用 distributedRemoteSharePath
直接 ifstream 读远程路径失败 未走 Resolve 缓存 必须调用 DistributedPathResolver::Resolve
targetNetworkId must be 64-char networkId 格式错误 传 64 字节 ID 或 "0"
服务进程不存在 secon 域未在 sepolicy 定义 配 sepolicy 或调试去掉 secon;宽容模式不能解决此问题
构建 process_field_validate 失败 uid: system 未入高权限白名单 high_privilege_process_list.json 登记服务

10. 附录:从图片场景迁移的注意点

若业务是图片,将 relativeSubdir 改为 "images/",源路径使用绝对路径(如 /data/images/photo.jpg)即可。


11. 源码

本章提供完整、可独立使用的 Demo 源码。按目录创建文件、编入工程并部署 helloworld_service.cfg 后,即可在两台已组网设备上验证分布式文件收发。

本文档提供 :HMDFS 工具库(发布 / share / 消费)+ Demo 业务封装(PublishDistributedFile / ReadDistributedFile)+ init cfg + BUILD.gn

本文档不提供 (需你自行准备):HelloWorld SA 的 sa_main 加载框架、helloworld_service.json profile、sepolicy .te 文件、触发 Demo 的 IPC/调试入口。你只需把第 11 章业务函数挂到已有 SA 的 OnStart 或调试命令回调中。

代码审查结论 :源码逻辑与已验证的 HMDFS 链路一致(落盘 → ioctl share → P2P → 本地缓存读取)。调通前提是第 0 章环境全部满足,尤其是软总线组网、GetLocalNetworkId() 非空、SELinux 宽容模式。

11.0 工程目录

复制代码
your_sa/
├── BUILD.gn
├── etc/
│   └── helloworld_service.cfg
├── hmdfs/
│   ├── hmdfs_file_publisher.h
│   ├── hmdfs_file_publisher.cpp
│   ├── hmdfs_share_helper.h
│   ├── hmdfs_share_helper.cpp
│   ├── distributed_path_resolver.h
│   └── distributed_path_resolver.cpp
└── services/
    ├── helloworld_distributed_file.h
    └── helloworld_distributed_file.cpp

services/helloworld_distributed_file.cpp 中的 PublishDistributedFile / ReadDistributedFile 挂到你已有的 HelloWorld SA 业务入口(IPC 回调、调试命令等)即可。


11.1 BUILD.gn

gn 复制代码
import("//build/ohos.gni")

ohos_static_library("hmdfs_helper") {
  use_exceptions = true
  sources = [
    "hmdfs/hmdfs_file_publisher.cpp",
    "hmdfs/hmdfs_share_helper.cpp",
    "hmdfs/distributed_path_resolver.cpp",
  ]
  include_dirs = [ "hmdfs" ]
  external_deps = [
    "dfs_service:distributed_file_daemon_kit_inner",
    "hilog:libhilog",
    "dsoftbus:softbus_client",
  ]
}

ohos_shared_library("libhelloworld_distributed_file") {
  sources = [
    "services/helloworld_distributed_file.cpp",
  ]
  include_dirs = [
    "hmdfs",
    "services",
  ]
  deps = [ ":hmdfs_helper" ]
  external_deps = [ "hilog:libhilog" ]
}

# 将 libhelloworld_distributed_file 加入你已有 HelloWorld SA 的 deps 即可

11.2 etc/helloworld_service.cfg

json 复制代码
{
    "jobs": [{
        "name": "service:helloworld_service",
        "cmds": [
            "mkdir /data/helloworld 0750 system system",
            "mkdir /data/service/el1/public/helloworld_cache 0750 system system",
            "chown system system /data/helloworld",
            "chown system system /data/service/el1/public/helloworld_cache"
        ]
    }],
    "services": [{
        "name": "helloworld_service",
        "path": ["/system/bin/sa_main", "/system/profile/helloworld_service.json"],
        "uid": "system",
        "gid": ["system", "shell", "dfs_share"],
        "jobs": {
            "on-start": "service:helloworld_service"
        },
        "caps": ["DAC_READ_SEARCH", "DAC_OVERRIDE", "CHOWN", "FOWNER"],
        "secon": "u:r:helloworld_service:s0",
        "sandbox": 0,
        "permission": [
            "ohos.permission.DISTRIBUTED_DATASYNC",
            "ohos.permission.DISTRIBUTED_SOFTBUS_CENTER"
        ]
    }]
}

11.3 services/helloworld_distributed_file.h

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 *
 * HelloWorld SA 分布式文件 Demo 业务接口。
 * 将本头文件的函数挂到你已有的 SA 入口即可。
 */
#ifndef HELLOWORLD_DISTRIBUTED_FILE_H
#define HELLOWORLD_DISTRIBUTED_FILE_H

#include <string>

namespace OHOS {
namespace DistributedFile {

// 发布本机文件,返回对端可访问的 distributedRemoteSharePath
bool PublishDistributedFile(const std::string& localPath, const std::string& peerNetworkId,
    std::string& remoteSharePath, std::string& errMsg);

// 根据对端传来的 remoteSharePath 读取文件内容
bool ReadDistributedFile(const std::string& remoteSharePath, std::string& content, std::string& errMsg);

// 获取本机 64 字节 networkId(调试打印用)
std::string GetLocalNetworkId();

} // namespace DistributedFile
} // namespace OHOS

#endif // HELLOWORLD_DISTRIBUTED_FILE_H

11.4 services/helloworld_distributed_file.cpp

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#include "helloworld_distributed_file.h"

#include "distributed_path_resolver.h"
#include "hmdfs_file_publisher.h"
#include "hmdfs_share_helper.h"

#include "hilog/log.h"

#include <fstream>
#include <iterator>

namespace OHOS {
namespace DistributedFile {

namespace {
constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001100, "HmdfsDemo" };
constexpr const char* SERVICE_NAME = "helloworld_service";
constexpr const char* DEFAULT_USER_ID = "100";
constexpr const char* DEMO_LOCAL_FILE = "/data/helloworld/hello.txt";
} // namespace

bool PublishDistributedFile(const std::string& localPath, const std::string& peerNetworkId,
    std::string& remoteSharePath, std::string& errMsg)
{
    HmdfsPublishOptions options;
    options.relativeSubdir = "share/";
    options.enableShareRegister = true;
    options.shareServiceName = SERVICE_NAME;
    options.targetNetworkId = peerNetworkId;

    HmdfsPublishResult result;
    if (!HmdfsFilePublisher::Publish(localPath, DEFAULT_USER_ID, result, errMsg, options)) {
        return false;
    }
    if (!result.shareRegistered) {
        errMsg = result.shareRegisterErr.empty() ? "share register failed" : result.shareRegisterErr;
        return false;
    }
    remoteSharePath = result.distributedRemoteSharePath;
    if (remoteSharePath.empty()) {
        errMsg = "distributedRemoteSharePath is empty";
        return false;
    }

    OHOS::HiviewDFX::HiLog::Info(LABEL, "Publish ok, local=%{public}s, remote=%{public}s",
        localPath.c_str(), remoteSharePath.c_str());
    return true;
}

bool ReadDistributedFile(const std::string& remoteSharePath, std::string& content, std::string& errMsg)
{
    DistributedPathRequest request;
    request.path = remoteSharePath;

    DistributedPathResult result;
    if (!DistributedPathResolver::Resolve(request, result, errMsg)) {
        return false;
    }

    std::ifstream input(result.localReadablePath, std::ios::binary);
    if (!input.is_open()) {
        errMsg = "open local cache failed: " + result.localReadablePath;
        return false;
    }
    content.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());

    OHOS::HiviewDFX::HiLog::Info(LABEL, "Read ok, cache=%{public}s, size=%{public}zu",
        result.localReadablePath.c_str(), content.size());
    return true;
}

std::string GetLocalNetworkId()
{
    return HmdfsFilePublisher::TryGetLocalNetworkId();
}

} // namespace DistributedFile
} // namespace OHOS

Demo 调用示例(设备 B 发布)

cpp 复制代码
#include "helloworld_distributed_file.h"

// 写入测试文件
{
    std::ofstream out("/data/helloworld/hello.txt");
    out << "hello from device B";
}

std::string remotePath;
std::string err;
if (OHOS::DistributedFile::PublishDistributedFile(
        "/data/helloworld/hello.txt", peerNetworkIdOnDeviceA, remotePath, err)) {
    // 将 remotePath 传给设备 A(日志 / IPC / RPC)
}

Demo 调用示例(设备 A 消费)

cpp 复制代码
std::string content;
std::string err;
if (OHOS::DistributedFile::ReadDistributedFile(remotePathFromB, content, err)) {
    // content == "hello from device B"
}

11.5 hmdfs/hmdfs_file_publisher.h

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef HMDFS_FILE_PUBLISHER_H
#define HMDFS_FILE_PUBLISHER_H

#include <string>

namespace OHOS {
namespace DistributedFile {

struct HmdfsPublishResult {
    std::string localPath;
    std::string hmdfsBackendPath;
    std::string hmdfsLocalViewPath;
    std::string distributedRelativePath;
    std::string path;
    std::string userId;
    std::string networkId;
    std::string distributedRemoteViewPath;
    bool copied {false};
    bool shareRegistered {false};
    std::string hmdfsShareLocalPath;
    std::string distributedRemoteSharePath;
    std::string shareRegisterErr;
};

struct HmdfsPublishOptions {
    std::string relativeSubdir {"share/"};
    bool enableShareRegister {true};
    std::string shareServiceName {"helloworld_service"};
    std::string shareTargetDeviceId {};
    std::string targetNetworkId {};
};

class HmdfsFilePublisher {
public:
    static bool Publish(const std::string& sourcePath, const std::string& userId,
        HmdfsPublishResult& result, std::string& errMsg,
        const HmdfsPublishOptions& options = {});

    static bool GetDirectoryInfo(const std::string& userId, const std::string& relativePath,
        HmdfsPublishResult& result, std::string& errMsg,
        const std::string& networkIdOverride = {});

    static std::string TryGetLocalNetworkId();

    static std::string BuildHmdfsBackendRoot(const std::string& userId);

    static std::string BuildHmdfsLocalViewRoot(const std::string& userId);

    static std::string BuildRemoteDeviceViewPath(
        const std::string& userId, const std::string& networkId, const std::string& relativePath);
};

} // namespace DistributedFile
} // namespace OHOS

#endif // HMDFS_FILE_PUBLISHER_H

11.6 hmdfs/hmdfs_file_publisher.cpp

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#include "hmdfs_file_publisher.h"

#include "hmdfs_share_helper.h"

#include "bus_center/softbus_bus_center.h"
#include "softbus_common.h"
#include "hilog/log.h"

#include <sys/stat.h>
#include <unistd.h>

#include <cerrno>
#include <cstring>
#include <fstream>
#include <vector>

namespace OHOS {
namespace DistributedFile {

namespace {

constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001101, "HmdfsFile" };
constexpr const char* HMDFS_MOUNT_PREFIX = "/mnt/hmdfs/";
constexpr const char* HMDFS_BACKEND_PREFIX = "/data/service/el2/";
constexpr const char* HMDFS_BACKEND_SUFFIX = "/hmdfs/account/files/";
constexpr const char* DEVICE_VIEW_SEGMENT = "/account/device_view/";
constexpr const char* FILES_SEGMENT = "/files/";

// 获取本机 networkId 时依次尝试的包名。
// 第一项为服务 cfg 中的 name;后两项为系统已注册的软总线包名(组网后通常可用)。
const char* kSoftbusPkgCandidates[] = {
    "helloworld_service",
    "ohos.storage.distributedfile.daemon",
    "ohos.dsoftbus.tool",
    nullptr,
};

bool StartsWith(const std::string& value, const char* prefix)
{
    return value.rfind(prefix, 0) == 0;
}

std::string StripLeadingSlash(std::string path)
{
    while (!path.empty() && path.front() == '/') {
        path.erase(path.begin());
    }
    return path;
}

std::string NormalizeRelativeSubdir(std::string subdir)
{
    subdir = StripLeadingSlash(subdir);
    if (!subdir.empty() && subdir.back() != '/') {
        subdir.push_back('/');
    }
    return subdir;
}

std::string BaseName(const std::string& path)
{
    const auto pos = path.find_last_of('/');
    if (pos == std::string::npos) {
        return path;
    }
    return path.substr(pos + 1);
}

bool IsPathReadable(const std::string& path)
{
    return !path.empty() && access(path.c_str(), R_OK) == 0;
}

bool EnsureParentDirs(const std::string& filePath)
{
    const auto pos = filePath.find_last_of('/');
    if (pos == std::string::npos) {
        return false;
    }

    std::string dir;
    for (size_t i = 0; i < pos; ++i) {
        if (filePath[i] == '/') {
            if (dir.empty()) {
                continue;
            }
            if (mkdir(dir.c_str(), 0755) != 0 && errno != EEXIST) {
                return false;
            }
        }
        dir.push_back(filePath[i]);
    }
    if (!dir.empty() && mkdir(dir.c_str(), 0755) != 0 && errno != EEXIST) {
        return false;
    }
    return true;
}

bool CopyFile(const std::string& sourcePath, const std::string& targetPath, std::string& errMsg)
{
    if (!EnsureParentDirs(targetPath)) {
        errMsg = std::string("create hmdfs parent dir failed: ") + std::strerror(errno);
        return false;
    }

    std::ifstream input(sourcePath, std::ios::binary);
    if (!input.is_open()) {
        errMsg = std::string("open source failed: ") + sourcePath + ", err=" + std::strerror(errno);
        return false;
    }

    std::ofstream output(targetPath, std::ios::binary | std::ios::trunc);
    if (!output.is_open()) {
        errMsg = std::string("open target failed: ") + targetPath + ", err=" + std::strerror(errno);
        return false;
    }

    output << input.rdbuf();
    if (!output.good()) {
        errMsg = "copy file to hmdfs failed";
        return false;
    }

    chmod(targetPath.c_str(), 0644);
    return true;
}

void FillPathFields(HmdfsPublishResult& result, const std::string& userId,
    const std::string& relativePath, const std::string& networkId)
{
    result.userId = userId;
    result.distributedRelativePath = relativePath;
    result.path = std::string("/") + relativePath;
    result.hmdfsBackendPath = HmdfsFilePublisher::BuildHmdfsBackendRoot(userId) + relativePath;
    result.hmdfsLocalViewPath = HmdfsFilePublisher::BuildHmdfsLocalViewRoot(userId) + relativePath;
    result.networkId = networkId;
    if (!networkId.empty()) {
        result.distributedRemoteViewPath =
            HmdfsFilePublisher::BuildRemoteDeviceViewPath(userId, networkId, relativePath);
    } else {
        result.distributedRemoteViewPath.clear();
    }
}

} // namespace

std::string HmdfsFilePublisher::BuildHmdfsBackendRoot(const std::string& userId)
{
    return std::string(HMDFS_BACKEND_PREFIX) + userId + HMDFS_BACKEND_SUFFIX;
}

std::string HmdfsFilePublisher::BuildHmdfsLocalViewRoot(const std::string& userId)
{
    return std::string(HMDFS_MOUNT_PREFIX) + userId + DEVICE_VIEW_SEGMENT + "local" + FILES_SEGMENT;
}

std::string HmdfsFilePublisher::BuildRemoteDeviceViewPath(
    const std::string& userId, const std::string& networkId, const std::string& relativePath)
{
    return std::string(HMDFS_MOUNT_PREFIX) + userId + DEVICE_VIEW_SEGMENT + networkId + FILES_SEGMENT +
        StripLeadingSlash(relativePath);
}

std::string HmdfsFilePublisher::TryGetLocalNetworkId()
{
    for (const char** candidate = kSoftbusPkgCandidates; *candidate != nullptr; ++candidate) {
        NodeBasicInfo localInfo;
        (void)memset(&localInfo, 0, sizeof(localInfo));
        const int32_t ret = ::GetLocalNodeDeviceInfo(*candidate, &localInfo);
        if (ret != 0 || localInfo.networkId[0] == '\0') {
            continue;
        }
        return std::string(localInfo.networkId);
    }
    return "";
}

bool HmdfsFilePublisher::GetDirectoryInfo(const std::string& userId, const std::string& relativePath,
    HmdfsPublishResult& result, std::string& errMsg, const std::string& networkIdOverride)
{
    result = HmdfsPublishResult {};
    errMsg.clear();

    const std::string normalizedUserId = userId.empty() ? "100" : userId;
    const std::string normalizedRelative = StripLeadingSlash(relativePath);
    if (normalizedRelative.empty()) {
        errMsg = "relativePath is required";
        return false;
    }

    const std::string networkId = networkIdOverride.empty() ? TryGetLocalNetworkId() : networkIdOverride;
    FillPathFields(result, normalizedUserId, normalizedRelative, networkId);
    return true;
}

bool HmdfsFilePublisher::Publish(const std::string& sourcePath, const std::string& userId,
    HmdfsPublishResult& result, std::string& errMsg, const HmdfsPublishOptions& options)
{
    result = HmdfsPublishResult {};
    errMsg.clear();

    if (sourcePath.empty()) {
        errMsg = "path is required";
        return false;
    }

    const std::string normalizedUserId = userId.empty() ? "100" : userId;
    const std::string relativeSubdir = NormalizeRelativeSubdir(options.relativeSubdir);

    if (!IsPathReadable(sourcePath)) {
        errMsg = "source file not readable, path=" + sourcePath;
        return false;
    }

    const std::string fileName = BaseName(sourcePath);
    if (fileName.empty()) {
        errMsg = "invalid file name";
        return false;
    }

    const std::string relativePath = relativeSubdir + fileName;
    const std::string targetBackendPath = BuildHmdfsBackendRoot(normalizedUserId) + relativePath;

    if (sourcePath == targetBackendPath) {
        result.copied = false;
    } else if (IsPathReadable(targetBackendPath)) {
        result.copied = false;
        OHOS::HiviewDFX::HiLog::Info(LABEL, "reuse existing hmdfs file %{public}s", targetBackendPath.c_str());
    } else if (!CopyFile(sourcePath, targetBackendPath, errMsg)) {
        return false;
    } else {
        result.copied = true;
        OHOS::HiviewDFX::HiLog::Info(LABEL, "published file to hmdfs %{public}s", targetBackendPath.c_str());
    }

    const std::string networkId = TryGetLocalNetworkId();
    result.localPath = sourcePath;
    FillPathFields(result, normalizedUserId, relativePath, networkId);

    if (!IsPathReadable(result.hmdfsBackendPath)) {
        errMsg = "hmdfs publish finished but backend file not readable: " + result.hmdfsBackendPath;
        return false;
    }

    if (options.enableShareRegister) {
        if (!options.targetNetworkId.empty() &&
            !HmdfsShareHelper::IsValidShareTargetId(options.targetNetworkId)) {
            errMsg = "targetNetworkId must be 64-char networkId or '0'";
            return false;
        }
        if (!options.shareTargetDeviceId.empty() &&
            !HmdfsShareHelper::IsValidShareTargetId(options.shareTargetDeviceId)) {
            errMsg = "shareTargetDeviceId must be 64-char networkId or '0'";
            return false;
        }

        std::string shareTarget = options.shareTargetDeviceId;
        if (shareTarget.empty() && !options.targetNetworkId.empty() &&
            HmdfsShareHelper::IsValidShareTargetId(options.targetNetworkId)) {
            shareTarget = options.targetNetworkId;
        }
        if (shareTarget.empty()) {
            shareTarget = HmdfsShareHelper::SHARE_ALL_DEVICE;
        }

        const std::string serviceName = options.shareServiceName.empty()
            ? HmdfsShareHelper::DEFAULT_SERVICE_NAME
            : options.shareServiceName;

        if (shareTarget == HmdfsShareHelper::SHARE_ALL_DEVICE && !options.targetNetworkId.empty() &&
            options.targetNetworkId != HmdfsShareHelper::SHARE_ALL_DEVICE) {
            std::string p2pErr;
            if (!HmdfsShareHelper::EnsureP2PConnection(options.targetNetworkId, p2pErr)) {
                OHOS::HiviewDFX::HiLog::Warn(LABEL, "P2P pre-connect (share all): %{public}s", p2pErr.c_str());
            }
        }

        std::string shareLocalPath;
        if (HmdfsShareHelper::RegisterShareFile(result.hmdfsBackendPath, normalizedUserId, serviceName,
                shareTarget, shareLocalPath, result.shareRegisterErr)) {
            result.shareRegistered = true;
            result.hmdfsShareLocalPath = shareLocalPath;
            if (!networkId.empty()) {
                result.distributedRemoteSharePath = HmdfsShareHelper::BuildRemoteShareViewPath(
                    normalizedUserId, networkId, serviceName, fileName);
            }
        } else {
            OHOS::HiviewDFX::HiLog::Warn(LABEL, "set_share_path failed: %{public}s",
                result.shareRegisterErr.c_str());
        }
    }

    return true;
}

} // namespace DistributedFile
} // namespace OHOS

11.7 hmdfs/hmdfs_share_helper.h

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef HMDFS_SHARE_HELPER_H
#define HMDFS_SHARE_HELPER_H

#include <string>

namespace OHOS {
namespace DistributedFile {

class HmdfsShareHelper {
public:
    static constexpr const char* SHARE_ALL_DEVICE = "0";
    static constexpr const char* DEFAULT_SERVICE_NAME = "helloworld_service";

    static bool RegisterShareFile(const std::string& sourceFilePath, const std::string& userId,
        const std::string& serviceName, const std::string& targetDeviceId, std::string& shareViewPath,
        std::string& errMsg);

    static std::string BuildLocalShareViewPath(const std::string& userId, const std::string& serviceName,
        const std::string& fileName);

    static std::string BuildRemoteShareViewPath(const std::string& userId, const std::string& publisherNetworkId,
        const std::string& serviceName, const std::string& fileName);

    static bool IsValidShareTargetId(const std::string& targetDeviceId);

    static bool EnsureP2PConnection(const std::string& peerNetworkId, std::string& errMsg);
};

} // namespace DistributedFile
} // namespace OHOS

#endif // HMDFS_SHARE_HELPER_H

11.8 hmdfs/hmdfs_share_helper.cpp

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#include "hmdfs_share_helper.h"

#include "distributed_file_daemon_manager.h"
#include "dm_device_info.h"
#include "hilog/log.h"

#include <climits>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <unistd.h>

namespace OHOS {
namespace DistributedFile {

namespace {

constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001102, "HmdfsShare" };
constexpr int HMDFS_CID_SIZE = 64;
constexpr unsigned HMDFS_IOC = 0xf2;
constexpr const char* HMDFS_MOUNT_PREFIX = "/mnt/hmdfs/";
constexpr const char* MERGE_SERVICES_SEGMENT = "/account/merge_view/services/";
constexpr const char* LOCAL_SERVICES_SEGMENT = "/account/device_view/local/services/";
constexpr const char* REMOTE_DEVICE_VIEW_SEGMENT = "/account/device_view/";
constexpr const char* SHARE_PATH_DIR = "/.share";
constexpr const char* SECURITY_XATTR = "user.security";
constexpr const char* DEFAULT_SECURITY_LABEL = "s0";

struct HmdfsShareControl {
    int fd;
    char deviceId[HMDFS_CID_SIZE];
};

#define HMDFS_IOC_SET_SHARE_PATH _IOW(HMDFS_IOC, 1, struct HmdfsShareControl)

bool EnsureDirectory(const std::string& path)
{
    if (path.empty()) {
        return false;
    }
    if (access(path.c_str(), F_OK) == 0) {
        return true;
    }
    return mkdir(path.c_str(), S_IRWXU) == 0 || errno == EEXIST;
}

bool CreateShareFile(int sourceFd, const std::string& ioctlDirRealPath, const std::string& targetDeviceId,
    std::string& errMsg)
{
    int dirFd = open(ioctlDirRealPath.c_str(), O_RDONLY);
    if (dirFd < 0) {
        errMsg = std::string("open share ioctl dir failed: ") + ioctlDirRealPath + ", err=" + std::strerror(errno);
        return false;
    }

    HmdfsShareControl shareControl {};
    shareControl.fd = sourceFd;
    std::memset(shareControl.deviceId, 0, HMDFS_CID_SIZE);
    if (targetDeviceId.size() > static_cast<size_t>(HMDFS_CID_SIZE)) {
        close(dirFd);
        errMsg = "target device id too long";
        return false;
    }
    std::memcpy(shareControl.deviceId, targetDeviceId.c_str(), targetDeviceId.size());

    if (ioctl(dirFd, HMDFS_IOC_SET_SHARE_PATH, &shareControl) < 0) {
        if (errno == EEXIST) {
            close(dirFd);
            return true;
        }
        errMsg = std::string("HMDFS_IOC_SET_SHARE_PATH failed, err=") + std::strerror(errno);
        close(dirFd);
        return false;
    }

    close(dirFd);
    return true;
}

bool SetShareSecurityLabel(const std::string& mergeShareDir, const std::string& fileName, const std::string& label,
    std::string& errMsg)
{
    const std::string fullPath = mergeShareDir + "/" + fileName;
    if (setxattr(fullPath.c_str(), SECURITY_XATTR, label.c_str(), label.size(), 0) != 0) {
        errMsg = std::string("setxattr ") + SECURITY_XATTR + " failed on " + fullPath + ", err=" + std::strerror(errno);
        return false;
    }
    return true;
}

} // namespace

bool HmdfsShareHelper::IsValidShareTargetId(const std::string& targetDeviceId)
{
    return targetDeviceId == SHARE_ALL_DEVICE || targetDeviceId.size() == static_cast<size_t>(HMDFS_CID_SIZE);
}

bool HmdfsShareHelper::EnsureP2PConnection(const std::string& peerNetworkId, std::string& errMsg)
{
    errMsg.clear();
    if (peerNetworkId.empty() || peerNetworkId == SHARE_ALL_DEVICE) {
        return true;
    }
    if (!IsValidShareTargetId(peerNetworkId)) {
        errMsg = "invalid peer networkId for P2P";
        return false;
    }
    if (peerNetworkId.size() >= DM_MAX_DEVICE_ID_LEN) {
        errMsg = "peer networkId too long";
        return false;
    }

    DistributedHardware::DmDeviceInfo deviceInfo {};
    std::memset(deviceInfo.networkId, 0, DM_MAX_DEVICE_ID_LEN);
    std::memcpy(deviceInfo.networkId, peerNetworkId.c_str(), peerNetworkId.size());

    const int32_t ret =
        Storage::DistributedFile::DistributedFileDaemonManager::GetInstance().OpenP2PConnection(deviceInfo);
    if (ret != 0) {
        errMsg = "OpenP2PConnection failed, ret=" + std::to_string(ret);
        OHOS::HiviewDFX::HiLog::Warn(LABEL, "%{public}s, peer=%{public}s", errMsg.c_str(), peerNetworkId.c_str());
        return false;
    }

    OHOS::HiviewDFX::HiLog::Info(LABEL, "P2P connected, peer=%{public}s", peerNetworkId.c_str());
    return true;
}

std::string HmdfsShareHelper::BuildLocalShareViewPath(const std::string& userId,
    const std::string& serviceName, const std::string& fileName)
{
    return std::string(HMDFS_MOUNT_PREFIX) + userId + MERGE_SERVICES_SEGMENT + serviceName + SHARE_PATH_DIR + "/" +
        fileName;
}

std::string HmdfsShareHelper::BuildRemoteShareViewPath(const std::string& userId,
    const std::string& publisherNetworkId, const std::string& serviceName, const std::string& fileName)
{
    return std::string(HMDFS_MOUNT_PREFIX) + userId + REMOTE_DEVICE_VIEW_SEGMENT + publisherNetworkId +
        "/services/" + serviceName + SHARE_PATH_DIR + "/" + fileName;
}

bool HmdfsShareHelper::RegisterShareFile(const std::string& sourceFilePath, const std::string& userId,
    const std::string& serviceName, const std::string& targetDeviceId, std::string& shareViewPath,
    std::string& errMsg)
{
    shareViewPath.clear();
    errMsg.clear();

    if (sourceFilePath.empty() || userId.empty() || serviceName.empty()) {
        errMsg = "invalid share register arguments";
        return false;
    }
    if (!IsValidShareTargetId(targetDeviceId)) {
        errMsg = "targetDeviceId must be '0' or 64-char networkId";
        return false;
    }

    if (targetDeviceId != SHARE_ALL_DEVICE) {
        std::string p2pErr;
        if (!EnsureP2PConnection(targetDeviceId, p2pErr)) {
            errMsg = "ensure P2P before share failed: " + p2pErr;
            return false;
        }
    }

    const std::string localServiceDir =
        std::string(HMDFS_MOUNT_PREFIX) + userId + LOCAL_SERVICES_SEGMENT + serviceName;
    const std::string localShareDir = localServiceDir + SHARE_PATH_DIR;
    if (!EnsureDirectory(localServiceDir) || !EnsureDirectory(localShareDir)) {
        errMsg = std::string("create local share dir failed: ") + localShareDir + ", err=" + std::strerror(errno);
        return false;
    }

    const std::string mergeShareDir =
        std::string(HMDFS_MOUNT_PREFIX) + userId + MERGE_SERVICES_SEGMENT + serviceName + SHARE_PATH_DIR;
    char realPath[PATH_MAX] = {};
    if (realpath(mergeShareDir.c_str(), realPath) == nullptr) {
        errMsg = std::string("realpath share dir failed: ") + mergeShareDir + ", err=" + std::strerror(errno);
        return false;
    }

    const auto pos = sourceFilePath.find_last_of('/');
    const std::string fileName = (pos == std::string::npos) ? sourceFilePath : sourceFilePath.substr(pos + 1);
    if (fileName.empty()) {
        errMsg = "invalid source file name";
        return false;
    }

    int sourceFd = open(sourceFilePath.c_str(), O_RDONLY);
    if (sourceFd < 0) {
        errMsg = std::string("open source for share failed: ") + sourceFilePath + ", err=" + std::strerror(errno);
        return false;
    }

    const bool ok = CreateShareFile(sourceFd, realPath, targetDeviceId, errMsg);
    close(sourceFd);
    if (!ok) {
        return false;
    }

    if (!SetShareSecurityLabel(mergeShareDir, fileName, DEFAULT_SECURITY_LABEL, errMsg)) {
        return false;
    }

    shareViewPath = BuildLocalShareViewPath(userId, serviceName, fileName);
    OHOS::HiviewDFX::HiLog::Info(LABEL, "share registered path=%{public}s target=%{public}s",
        shareViewPath.c_str(), targetDeviceId.c_str());
    return true;
}

} // namespace DistributedFile
} // namespace OHOS

11.9 hmdfs/distributed_path_resolver.h

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef DISTRIBUTED_PATH_RESOLVER_H
#define DISTRIBUTED_PATH_RESOLVER_H

#include <string>

namespace OHOS {
namespace DistributedFile {

enum class PathSourceType {
    LOCAL = 0,
    HMDFS_REMOTE_VIEW,
    HMDFS_LOCAL_VIEW,
    HMDFS_REMOTE_SHARE,
    UNKNOWN,
};

struct DistributedPathRequest {
    std::string path;
    std::string userId {"100"};
};

struct DistributedPathResult {
    PathSourceType sourceType {PathSourceType::UNKNOWN};
    std::string originalPath;
    std::string resolvedPath;
    std::string localReadablePath;
    std::string networkId;
    bool copiedToCache {false};
};

class DistributedPathResolver {
public:
    static bool Resolve(const DistributedPathRequest& request, DistributedPathResult& result, std::string& errMsg);

    static PathSourceType DetectPathSourceType(const DistributedPathRequest& request);

private:
    static bool IsPathReadable(const std::string& path);
    static bool CopyToLocalCache(const std::string& sourcePath, std::string& cachePath, std::string& errMsg);
    static std::string DefaultCacheDir();
    static bool ExtractNetworkIdFromRemoteViewPath(const std::string& mountPath, std::string& networkId);
};

} // namespace DistributedFile
} // namespace OHOS

#endif // DISTRIBUTED_PATH_RESOLVER_H

11.10 hmdfs/distributed_path_resolver.cpp

cpp 复制代码
/*
 * Copyright (c) 2026
 * SPDX-License-Identifier: Apache-2.0
 */
#include "distributed_path_resolver.h"

#include "hmdfs_share_helper.h"
#include "hilog/log.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <cerrno>
#include <cstring>
#include <fstream>

namespace OHOS {
namespace DistributedFile {

namespace {

constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001103, "DistributedPath" };
constexpr const char* HMDFS_MOUNT_PREFIX = "/mnt/hmdfs/";
constexpr const char* DEVICE_VIEW_SEGMENT = "/account/device_view/";
constexpr const char* SHARE_PATH_SEGMENT = "/.share/";
constexpr const char* LOCAL_CACHE_DIR = "/data/service/el1/public/helloworld_cache";

bool StartsWith(const std::string& value, const char* prefix)
{
    return value.rfind(prefix, 0) == 0;
}

bool Contains(const std::string& value, const char* token)
{
    return value.find(token) != std::string::npos;
}

std::string BaseName(const std::string& path)
{
    const auto pos = path.find_last_of('/');
    if (pos == std::string::npos) {
        return path;
    }
    return path.substr(pos + 1);
}

} // namespace

PathSourceType DistributedPathResolver::DetectPathSourceType(const DistributedPathRequest& request)
{
    if (StartsWith(request.path, HMDFS_MOUNT_PREFIX) && Contains(request.path, DEVICE_VIEW_SEGMENT)) {
        if (Contains(request.path, "/device_view/local/")) {
            return PathSourceType::HMDFS_LOCAL_VIEW;
        }
        if (Contains(request.path, SHARE_PATH_SEGMENT)) {
            return PathSourceType::HMDFS_REMOTE_SHARE;
        }
        return PathSourceType::HMDFS_REMOTE_VIEW;
    }
    if (StartsWith(request.path, "/")) {
        return PathSourceType::LOCAL;
    }
    return PathSourceType::UNKNOWN;
}

bool DistributedPathResolver::ExtractNetworkIdFromRemoteViewPath(
    const std::string& mountPath, std::string& networkId)
{
    constexpr const char* deviceViewSegment = "/account/device_view/";
    constexpr const char* filesSegment = "/files/";
    constexpr const char* servicesSegment = "/services/";
    const auto markerPos = mountPath.find(deviceViewSegment);
    if (markerPos == std::string::npos) {
        return false;
    }
    const size_t pos = markerPos + std::strlen(deviceViewSegment);
    if (mountPath.compare(pos, 6, "local/") == 0) {
        return false;
    }

    const auto filesPos = mountPath.find(filesSegment, pos);
    if (filesPos != std::string::npos && filesPos > pos) {
        networkId = mountPath.substr(pos, filesPos - pos);
        return !networkId.empty();
    }

    const auto servicesPos = mountPath.find(servicesSegment, pos);
    if (servicesPos != std::string::npos && servicesPos > pos) {
        networkId = mountPath.substr(pos, servicesPos - pos);
        return !networkId.empty();
    }

    return false;
}

std::string DistributedPathResolver::DefaultCacheDir()
{
    return LOCAL_CACHE_DIR;
}

bool DistributedPathResolver::IsPathReadable(const std::string& path)
{
    if (path.empty()) {
        return false;
    }
    const int fd = open(path.c_str(), O_RDONLY);
    if (fd < 0) {
        return false;
    }
    char buf[1];
    const ssize_t n = read(fd, buf, 1);
    close(fd);
    return n > 0;
}

bool DistributedPathResolver::CopyToLocalCache(
    const std::string& sourcePath, std::string& cachePath, std::string& errMsg)
{
    const std::string cacheDir = DefaultCacheDir();
    if (mkdir(cacheDir.c_str(), 0755) != 0 && errno != EEXIST) {
        errMsg = std::string("create cache dir failed: ") + std::strerror(errno);
        return false;
    }

    cachePath = cacheDir + "/" + BaseName(sourcePath);
    std::ifstream input(sourcePath, std::ios::binary);
    if (!input.is_open()) {
        errMsg = std::string("open source failed: ") + sourcePath + ", err=" + std::strerror(errno);
        return false;
    }

    std::ofstream output(cachePath, std::ios::binary | std::ios::trunc);
    if (!output.is_open()) {
        errMsg = std::string("open cache failed: ") + cachePath + ", err=" + std::strerror(errno);
        return false;
    }

    output << input.rdbuf();
    if (!output.good()) {
        errMsg = "copy file to cache failed";
        return false;
    }

    chmod(cachePath.c_str(), 0644);
    OHOS::HiviewDFX::HiLog::Info(LABEL, "copied to cache, src=%{public}s, cache=%{public}s",
        sourcePath.c_str(), cachePath.c_str());
    return true;
}

bool DistributedPathResolver::Resolve(
    const DistributedPathRequest& request, DistributedPathResult& result, std::string& errMsg)
{
    result = DistributedPathResult {};
    result.originalPath = request.path;
    result.sourceType = DetectPathSourceType(request);
    ExtractNetworkIdFromRemoteViewPath(request.path, result.networkId);

    switch (result.sourceType) {
        case PathSourceType::LOCAL:
        case PathSourceType::HMDFS_REMOTE_VIEW:
        case PathSourceType::HMDFS_REMOTE_SHARE:
        case PathSourceType::HMDFS_LOCAL_VIEW:
            result.resolvedPath = request.path;
            break;
        default:
            errMsg = "unsupported path format, expect HMDFS remote share path";
            return false;
    }

    OHOS::HiviewDFX::HiLog::Info(LABEL,
        "resolve path, type=%{public}d, path=%{public}s, networkId=%{public}s",
        static_cast<int>(result.sourceType), result.originalPath.c_str(), result.networkId.c_str());

    if ((result.sourceType == PathSourceType::HMDFS_REMOTE_VIEW ||
            result.sourceType == PathSourceType::HMDFS_REMOTE_SHARE) &&
        !result.networkId.empty()) {
        std::string p2pErr;
        if (!HmdfsShareHelper::EnsureP2PConnection(result.networkId, p2pErr)) {
            errMsg = "ensure HMDFS P2P failed: " + p2pErr;
            return false;
        }
        if (CopyToLocalCache(result.resolvedPath, result.localReadablePath, errMsg)) {
            result.copiedToCache = true;
            return true;
        }
        errMsg = "distributed file not accessible, networkId=" + result.networkId +
            ", resolved=" + result.resolvedPath;
        return false;
    }

    if (IsPathReadable(result.resolvedPath)) {
        result.localReadablePath = result.resolvedPath;
        return true;
    }

    if (CopyToLocalCache(result.resolvedPath, result.localReadablePath, errMsg)) {
        result.copiedToCache = true;
        return true;
    }

    errMsg = "file not readable: " + result.resolvedPath;
    return false;
}

} // namespace DistributedFile
} // namespace OHOS

11.11 接入检查清单

环境(第 0 章)

  1. 两台设备均已烧录 SN 号且系统已激活
  2. 两台设备软总线组网成功,getenforce 为 Permissive
  3. mount | grep hmdfs 有输出
  4. 两台设备 GetLocalNetworkId() 均返回非空 64 字节字符串

工程(第 11 章)

  1. 创建第 11.0 节目录,将 11.1~11.10 源码写入对应文件
  2. libhelloworld_distributed_file 编入你已有的 HelloWorld SA 工程
  3. 部署 helloworld_service.cfg,确认 giddfs_share
  4. uidsystem,在 high_privilege_process_list.json 登记服务名
  5. 修改 cfg 后 reboot 或 stop/start helloworld_service

联调

  1. 设备 B:写 /data/helloworld/hello.txt,调用 PublishDistributedFile,日志中应有非空 remoteSharePath
  2. remoteSharePath 传给设备 A(调试可直接复制日志中的路径)
  3. 设备 A:调用 ReadDistributedFile,确认 content == "hello from device B"

常见卡点

卡点 检查
distributedRemoteSharePath is empty GetLocalNetworkId() 为空 → 回到 0.2 节确认 SN 烧录、系统激活与组网
create local share dir failed 进程无 dfs_share gid → 重启服务
OpenP2PConnection failed 对端 offline、SN/激活异常、或无 DISTRIBUTED_DATASYNC 权限
distributed file not accessible 未先发布、路径抄错、或 share 已过期(约 120s)
服务起不来 secon 域未定义 → 配 sepolicy 或调试去掉 secon
相关推荐
●VON8 分钟前
鸿蒙 PC Markdown 编辑器文件系统:Core File Kit 与安全保存
安全·华为·编辑器·harmonyos·鸿蒙
YM52e42 分钟前
鸿蒙 Flutter 渐变效果详解:LinearGradient、RadialGradient、SweepGradient
android·学习·flutter·华为·harmonyos·鸿蒙
YM52e44 分钟前
鸿蒙 Flutter BoxDecoration装饰:打造精美UI效果
学习·flutter·ui·华为·harmonyos·鸿蒙
●VON1 小时前
鸿蒙 PC Markdown 编辑器性能工程:中文输入与 10MiB 文档
华为·架构·编辑器·harmonyos·鸿蒙
qq_463408421 小时前
# 基于 HarmonyOS ArkTS 声明式 UI 构建智能音乐播放器:深色主题圆盘轮播、五 Tab 导航隔离、瀑布流收藏与悬浮迷你播放条实现深度解析
ui·华为·harmonyos
YM52e3 小时前
鸿蒙Flutter Card组件:Material设计风格卡片
android·学习·flutter·华为·harmonyos·鸿蒙
ldsweet11 小时前
《HarmonyOS技术精讲-Basic Services Kit》上传下载进阶:多任务并发与后台长时任务
华为·harmonyos
qizayaoshuap12 小时前
# [特殊字符] 宠物模拟器 — 鸿蒙ArkTS完整技术解析(最终篇)
华为·harmonyos·宠物
不言鹅喻12 小时前
HarmonyOS ArkTS 实战:实现一个校园自动售货机库存与补货记录应用
华为·harmonyos
世人万千丶13 小时前
鸿蒙Flutter TextStyle样式配置
学习·flutter·harmonyos·鸿蒙