Windows + Git Bash 下用 hdc 推文件到鸿蒙应用私有沙箱的四个连环坑
关键词:HarmonyOS / OpenHarmony、hdc、file send、EL2 沙箱、MSYS_NO_PATHCONV、SELinux、root 设备
适用读者:用 Windows + DevEco SDK 工具链做鸿蒙应用调试的开发者,遇到过
hdc file send报permission denied/no such file or directory不知道卡在哪一层的同学。
前言
调试鸿蒙应用时经常需要把一份准备好的测试数据(缓存、配置、mock 响应等)预置到自家应用的 EL2 私有沙箱里,让 App 启动时读这份预置数据走特定分支。
听起来就是一条 hdc file send 的事。实际做下来在 Windows + Git Bash 环境下会踩到 4 个独立的坑连成一串,每一坑的报错看起来都像是另一个问题,换方向排查半天才摸清楚根因。本文把整条路径一次讲清,给出最终可用命令和一眼排查表。
前置条件 & 适用范围
- 只推到你自己开发的、debug 签名的 App 沙箱。正式固件下 hdc 无权访问任何应用(包括系统应用)的私有数据,这是 OS 安全模型的一部分;本文讨论的是 userdebug / 开发机 / 已 root 设备上给自己的 App 注入调试数据。
- 示例环境:HarmonyOS 5.0(API 12)/ OpenHarmony、DevEco Studio 或独立 hvigor CLI、Windows 10/11 + Git Bash(MSYS2)。
- 如果你在 macOS / Linux 或纯 cmd/PowerShell 下,坑 1 和坑 2 的形态会略有不同,但解法思路通用。
一、最终可用命令(TL;DR)
假设你已经满足前置条件(自家 debug App、已 root 设备或 userdebug 固件)。
# 0. 先设好环境变量,避免后面命令冗长(你的实际 SDK 路径)
export DEVECO_SDK_HOME="/path/to/your/DevEco/sdk"
export HDC="$DEVECO_SDK_HOME/default/openharmony/toolchains/hdc.exe"
# 1. cd 到源文件所在目录(不能用绝对路径喂给 hdc — 见坑 1)
cd "C:/Users/<YourName>/Downloads"
# 2. 推文件(禁用 MSYS 路径转换 — 见坑 2)
MSYS_NO_PATHCONV=1 "$HDC" file send \
"data.json" \
"/data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json"
# 3. 校正 owner/权限为目标应用 uid(查自己的 uid 方法见下文)
MSYS_NO_PATHCONV=1 "$HDC" shell "
chown <APP_UID>:<APP_UID> /data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json &&
chmod 660 /data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json &&
ls -la /data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json
"<APP_UID> 查法:
MSYS_NO_PATHCONV=1 "$HDC" shell "ls -ld /data/app/el2/100/base/com.example.myapp/haps/entry/files"
# drwxrwx--- 2 20020124 20020124 ...
# └─────┬────┘
# 应用 uid:gid(每台设备、每个应用都不同)二、四个连环坑(按遇到顺序)
坑 1:hdc 把 Windows 绝对路径拼接到当前工作目录后面
现象
hdc file send "C:/Users/Alice/Downloads/data.json" "/data/..."
# [Fail] Error opening file: no such file or directory,
# path:D:\your\current\workdir\C:/Users/Alice/Downloads/data.json仔细看报错里的路径:D:\当前工作目录\C:/Users/Alice/... — hdc 把已经是绝对路径的本地参数又拼在了 CWD 后面,形成 Windows 根本识别不了的畸形路径。
原因:hdc 对本地文件参数做了 CWD 相对路径解析,但没处理"参数已经是绝对路径"这种分支,Windows 盘符根(C:、D:)的引入也加剧了这个问题。
解法:先 cd 到源文件目录,用相对路径传给 hdc:
cd "C:/Users/Alice/Downloads"
hdc file send "data.json" "/data/..."同样的问题也出现在 hdc install,官方文档没明说但踩过的人都知道。坑 2:Git Bash 的 MSYS 路径转换把设备端路径改坏了
跨过坑 1 后你会遇到更诡异的:
现象
cd "C:/Users/Alice/Downloads"
hdc file send "data.json" "/data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json"
# [Fail] open path:C:/Program Files/Git/data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json设备端的 /data/app/... 变成了 Windows 上的 C:/Program Files/Git/data/app/...。
原因:Git Bash 基于 MSYS2,它对所有"看起来像 Unix 绝对路径"(以 / 开头)的命令行参数会自动做一次路径翻译,猜测这是 POSIX 挂载点,把前缀替换成对应的 Windows 路径。C:/Program Files/Git/ 是 Git for Windows 的安装根。
hdc 的设备端路径跟本地路径长得一模一样(都以 / 开头),所以也被一视同仁地翻译了,结果变成了一个无效的本地路径。
解法:用 MSYS_NO_PATHCONV=1 临时关闭路径转换:
MSYS_NO_PATHCONV=1 hdc file send "data.json" "/data/app/.../data.json"其他等价方案:
- 双斜杠前缀:
//data/app/...(MSYS 遇到//会保留原样)- 切换到 cmd / PowerShell 执行
推荐
MSYS_NO_PATHCONV=1,显式、局部、可 grep,后来人读脚本一看就知道为什么加它。
坑 3:/data/app/el2/... 是应用私有沙箱,普通 hdc shell 碰不到
跨过坑 2 后,如果你的设备是正式发布固件,还会撞上:
现象
hdc shell "id"
# uid=2000(shell) gid=2000(shell) ... context=u:r:sh:s0
hdc file send data.json /data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json
# [Fail] permission denied
hdc shell "ls /data/app/el2/100/base/com.example.myapp"
# ls: ...: Permission denied(连目录存在都看不到)原因:
- 鸿蒙正式发布固件下,hdc 以
uid=2000(shell)、SELinux 域u:r:sh:s0运行 /data/app/el2/<用户 ID>/base/<包名>/是每个应用的 EL2 加密私有目录,权限通常是drwxrwx---,owner 是应用自己的 uid(例如20020124),shell 既不在 owner 也不在 group,DAC 直接拒- 即使想靠
su提权,which su没有、param get const.debuggable也失败,说明是发布固件,没有提权路径
解法:只有两条路
- 换 userdebug 固件 / 已 root 的开发机(验证手段见坑 4)
走
/data/local/tmp/中转 + 应用内拷贝:- 把文件
hdc file send到/data/local/tmp/(shell 可读写) - 由你自己的 App(debug 签名)读
/data/local/tmp/data.json再写到自家filesDir - 适用于你的 App 本身就装了调试逻辑;任何正式固件都能用,不需要 root
- 把文件
方案 2 不需要 root 但需要你的 App 配合一段"从 tmp 拷贝到沙箱"的调试代码。生产发布前务必用构建类型门控掉这段代码。
坑 4:如何"真·验证 root"(不能只看 uid)
假设换到了 root 设备或 userdebug 固件,hdc shell "id" 显示 uid=0(root) 就能信吗?不能。存在两类"假 root":
- 只改了进程凭据 uid,但 SELinux 拦死所有敏感操作 → 实际啥也干不了
- 开了 cap 但不是完整 root → 某些 syscall/路径仍被拒
单看 id 不够。用三重交叉证据法:
hdc shell "id; ls -ld /data/app/el2/100/base/com.example.myapp/haps/entry/files"期望输出:
uid=0(root) gid=0(root) ... context=u:r:su:s0 ← 证据 1 + 2
drwxrwx--- 2 20020124 20020124 ... /data/app/.../files ← 证据 3| 证据 | 含义 | 为什么不能只看这一项 |
|---|---|---|
uid=0(root) | 进程凭据是 root | uid 可被改,SELinux 会拦 |
context=u:r:su:s0 | SELinux 域是 su(不是 sh) | 内核 MAC 层也认这个 root,才有实际越权能力 |
ls -ld 能列出私有沙箱 | 实战穿透 DAC 权限位 | 前两条过了但仍可能是受限 root(只给部分 cap) |
反例(非 root 或假 root):
uid=2000(shell) ... context=u:r:sh:s0 ← 纯 shell 用户
ls: ...: Permission denied
或
uid=0(root) ... context=u:r:sh:s0 ← uid 像 root 但 SELinux 仍是 sh 域
ls: ...: Permission denied三条里任一条不过 = 不能信其写沙箱的能力,换固件。
三、沙箱路径解读
/data/app/el2/100/base/com.example.myapp/haps/entry/files/
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ Context.filesDir 对应的目录
│ │ │ │ │ └─────── HAP 模块名(entry 是主模块默认名)
│ │ │ │ └──────────── haps 下按模块分
│ │ │ └───────────────────────────────── 应用包名
│ │ └────────────────────────────────────── base = 应用基础沙箱
│ └─────────────────────────────────────────── 用户 ID(主用户 = 100)
└─────────────────────────────────────────────── el2 = 二级加密分区(锁屏解锁后可访问)另一常见分区是 el1(开机即可访问,适合系统级数据),应用自己的数据通常落在 el2。其他 Context 子目录:
files/↔context.filesDircache/↔context.cacheDirpreferences/↔SharedPreferences(SPUtils类落地位置)database/↔@kit.ArkData关系数据库
推文件前看一眼你的 App 实际读的是哪个子目录,别推错地方。
四、owner / 权限修正
hdc file send 推过去的文件,不同版本 hdc / 固件表现不一样:
- 有的版本会自动把 owner 设成目标应用 uid(好的情况)
- 有的版本 owner 是 root 或 shell,应用进程(以
<APP_UID>运行)读不到
建议不管哪种情况都显式修一遍:
# 先查应用 uid(目标 files 目录的 owner 就是)
hdc shell "ls -ld /data/app/el2/100/base/com.example.myapp/haps/entry/files"
# drwxrwx--- 2 20020124 20020124 ...
# 按此修复新推文件
hdc shell "
chown 20020124:20020124 /data/app/.../data.json &&
chmod 660 /data/app/.../data.json
"权限给 660 是因为应用访问沙箱时以它自己 uid + 同名 gid 访问,其他用户不需要读写权限。不要图省事给 666 或 777,违反最小权限原则。
五、可复用命令模板
换好下面 6 个变量,整块粘贴即可:
# ---- 配置 ----
LOCAL_DIR="C:/Users/<YourName>/Downloads" # 本地文件所在目录
LOCAL_FILE="data.json" # 本地文件名
TARGET_PKG="com.example.myapp" # 你的 debug 应用包名
TARGET_MODULE="entry" # HAP 模块名,默认 entry
TARGET_USER="100" # 多用户系统里的用户 ID,主用户 100
HDC="/path/to/DevEco/sdk/default/openharmony/toolchains/hdc.exe"
TARGET_DIR="/data/app/el2/${TARGET_USER}/base/${TARGET_PKG}/haps/${TARGET_MODULE}/files"
TARGET_PATH="${TARGET_DIR}/${LOCAL_FILE}"
# ---- 1. 验证设备是 root + 目标目录可访问 ----
MSYS_NO_PATHCONV=1 "$HDC" shell "id; ls -ld ${TARGET_DIR}"
# 期望:uid=0(root) ... context=u:r:su:s0 + 能列出目录详情(记下里面的 uid)
# 把上一步查到的 uid 填到这里
TARGET_UID="20020124"
# ---- 2. 推文件 ----
cd "$LOCAL_DIR" && \
MSYS_NO_PATHCONV=1 "$HDC" file send "$LOCAL_FILE" "$TARGET_PATH"
# ---- 3. 校正 owner/权限并验证 ----
MSYS_NO_PATHCONV=1 "$HDC" shell "
chown ${TARGET_UID}:${TARGET_UID} ${TARGET_PATH} &&
chmod 660 ${TARGET_PATH} &&
ls -la ${TARGET_PATH}
"六、一眼失败排查表
| 报错片段 | 落在哪一坑 | 修复 |
|---|---|---|
open path:<CWD>\C:/... 本地路径被拼在 CWD 后面 | 坑 1 | cd 到源目录,用相对路径 |
open path:C:/Program Files/Git/data/... 设备端路径被改成本地路径 | 坑 2 | 命令前加 MSYS_NO_PATHCONV=1 |
permission denied + id 显示 uid=2000(shell) | 坑 3 | 换 userdebug 固件/root 开发机,或走 /data/local/tmp/ 中转 |
uid=0(root) 但 ls 仍 Permission denied | 坑 4 | 假 root / 受限 root(SELinux 域不是 su),换真正的 userdebug 固件 |
| 文件推过去了但应用读报错 | owner/权限 | chown <APP_UID>:<APP_UID> + chmod 660 |
| 应用读到空 / 旧数据 | EL2 未解锁 | 设备重启后要先解锁屏幕,EL2 分区才挂载 |
七、延伸阅读
- SELinux 在 Android/鸿蒙这类系统里的作用:Mandatory Access Control,内核级强制访问控制,独立于 DAC(user/group 权限位)。即使 uid=0,只要 SELinux 域不允许,该操作仍会失败。
/data/app/el1vs/data/app/el2:EL1 开机即可用,EL2 需锁屏解锁后才挂载。重启设备后如果一直没解锁,所有 EL2 路径都访问不到。hdc的命令全集和选项:查你本地 SDK 里toolchains/下的hdc.exe --help。
免责声明
- 本文示例中的
com.example.myapp是占位符,请替换为你自己开发的、debug 签名的 App 包名。不要尝试用这套流程写其他厂商的系统应用或第三方 App 的沙箱 —— 在正式固件上这会被 OS 拒掉,在开发机上虽然可能成功但不符合应用隔离的设计意图,也可能违反相关使用条款。 - 任何在
/data/local/tmp/→ 自家沙箱中转方案都只能用于 debug 构建;生产发布前用构建类型(BuildProfile / product / flavor)彻底移除调试通路。 - 作者不对读者在自己设备上执行本文命令产生的任何数据丢失或设备异常负责。涉及 root / userdebug 设备的操作请在了解设备保修状态后自行决策。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。