系统服务分布式文件(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 设为宽容模式,避免 ioctl、setxattr、写 HMDFS 路径等环节被 AVC 拒绝。
查看当前模式(PC 端执行):
bash
hdc shell getenforce
输出 Permissive 或 0 表示宽容模式;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 与构建校验
| 条件 | 说明 |
|---|---|
gid 含 dfs_share |
否则 share 目录 mkdir / ioctl 报 Permission denied |
| 分布式权限 | DISTRIBUTED_DATASYNC、DISTRIBUTED_SOFTBUS_CENTER |
sandbox: 0 |
系统服务需直接访问 /mnt/hmdfs/、/data/service/el2/ |
uid 为 system 时 |
产品的 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.cfg 的 services 段补充以下字段(完整示例见第 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.cpp 的 DefaultCacheDir() 中修改)。
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 章):
- 两台设备均已烧录 SN 号且系统已激活
- 两台设备软总线组网成功,
getenforce为 Permissive mount | grep hmdfs有输出- 两台设备
GetLocalNetworkId()均返回非空 64 字节字符串
工程(第 11 章):
- 创建第 11.0 节目录,将 11.1~11.10 源码写入对应文件
- 将
libhelloworld_distributed_file编入你已有的 HelloWorld SA 工程 - 部署
helloworld_service.cfg,确认gid含dfs_share - 若
uid为system,在high_privilege_process_list.json登记服务名 - 修改 cfg 后 reboot 或 stop/start helloworld_service
联调:
- 设备 B:写
/data/helloworld/hello.txt,调用PublishDistributedFile,日志中应有非空remoteSharePath - 将
remoteSharePath传给设备 A(调试可直接复制日志中的路径) - 设备 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 |