问题分析
1. 现象
- 双击
/s500data/fileJob/1606atibm/2105oracle/x99/bak/x99_20260829145835.tar.gz,在 Ark 里打开该压缩包。 - 在 Ark 界面把包内目录
www/frpc拖到 Dolphin 的/s500data/docker/,松手释放鼠标。 - Ark 界面显示「正在解压 3 个文件」,全程无任何弹窗/交互提示;进度跑完后
/s500data/docker/里没有任何新增的frpc目录内容。 - 同样的拖拽操作,在别的压缩包(成员名是相对路径的那种)上是正常的。
2. 结论
根因在上游 Ark(26.04.3),不是 NixOS、不是文件权限、不是 KIO/Dolphin、不是磁盘空间、也不是弹窗被挡住。
具体:当压缩包内的成员名带前导「/」(绝对路径,例如 /www/frpc/docker-compose.yml)时,「按选中条目解压」这一步永远匹配不到条目 ⇒ 静默解出 0 个文件,而进度条仍照报「正在解压 N 个文件」。
同一链路上「解压所有 / 整包拖」走的是另一分支,不受影响(实测同一个包整包解压:2771 个文件 / 600 MB 全部正确落地)。
3. 调用链路(谁在做什么)
- Ark 的拖拽不带文件 URL,只带两个私有 MIME:
application/x-kde-ark-dndextract-service(发送方 D-Bus 服务名)与application/x-kde-ark-dndextract-path(对象路径)——ark/part/archivemodel.cpp::ArchiveModel::mimeData()。 - Ark 在
Part构造时注册 D-Bus 对象/DndExtract/<n>,接口org.kde.ark.DndExtract,唯一方法extractSelectedFilesTo(QString path)。 - Dolphin 自己处理这个 MIME:
src/views/draganddrophelper.cpp::DragAndDropHelper::dropUrls()直接发起同步 D-Bus 调用extractSelectedFilesTo(目标目录),不经过 KIO 的 DropJob;非 Dolphin 的目标才走 KIO 兜底分支kio/src/widgets/dropjob.cpp,逻辑相同。 - Ark 侧
Part::extractSelectedFilesTo()用ExtractionOptions::setDragAndDropEnabled(true)起 ExtractJob。 .tar.gz由 kerfuffle 的 libarchive 插件处理。strace实测:没有任何外部tar/unar子进程,只有 Ark 自己chdir(目标目录),也没有.ark-XXXX临时目录 ⇒ 那条「临时目录 + moveDroppedFilesToDest」的路径只适用于 7z/rar/zip 等 CliInterface 插件,对 tar.* 不成立。
4. 代码上的不一致(根本成因)
文件:plugins/libarchive/libarchiveplugin.cpp(v26.04.3),两处对「列出」与「抽取」的路径处理不一致:
| # | 位置 | 行为 |
|---|---|---|
| R01 | list() → emitEntryFromArchiveEntry():e->setProperty("fullPath", archive_entry_pathname(...)) | 成员名原样进模型 ⇒ 绝对路径归档的模型路径是 /www/frpc、/www/frpc/docker-compose.yml |
| R02 | extractFiles():先 while (entryName.startsWith('/')) entryName.remove(0,1),再 remainingFiles.contains(entryName) / fullPaths.indexOf(entryName),不命中则 continue | 抽取侧剥掉开头「/」再与模型路径做全等匹配 ⇒ www/frpc ≠ /www/frpc ⇒ 每个条目都被跳过,0 个被解出且不报错 |
| R03 | 进度数字来源:totalEntriesCount = files.size() | 等于「所选条目数」(该目录 + 2 个文件 = 3),与实际写出多少无关 ⇒ 所以会一本正经显示「正在解压 3 个文件」然后目标里一个字节都没有 |
| R04 | extractAll 分支(未选任何条目 / 整包拖) | 完全绕过 R02 那段匹配 ⇒ 整包解压永远正常,这也是「同一个包两种结果」的原因 |
5. 证据
5.1 真实拖拽的现场抓取(dbus-monitor + 50 ms 全树轮询 + Ark 的 fd 扫描)
| # | 抓取项 | 结果 |
|---|---|---|
| R05 | Dolphin 实际传给 Ark 的目标目录字符串 | extractSelectedFilesTo "/s500data/docker" —— 完全正确(不是 URL、不是空、不是别的目录) |
| R06 | 拖拽后 /s500data/docker 下的新增/删除(50 ms 粒度、含隐藏项) | 零(连临时目录都没有出现过) |
| R07 | /s500data/docker 的 mtime | 仍是 13:48,而拖拽发生在 14:16:35 ⇒ 那次一个条目都没落盘 |
| R08 | 全盘反向搜索 13:40 之后的 frpcv100.toml / .ark-* | 无(/s500data、/home/cat、/tmp 都查过) |
5.2 对照实验(同一个压缩包、同一条 D-Bus 调用)
| # | 做法 | 结果 |
|---|---|---|
| R09 | 选中包内一个目录(如根级 home)→ 解到空临时目录 | ❌ 什么都不写,返回码 0,无报错、无弹窗(复现用户症状) |
| R10 | 不选任何条目(= 解压全部)→ 解到空临时目录 | ✅ 2771 个文件 / 600 MB 全部落地,www/frpc/docker-compose.yml、www/frpc/frpcv100.toml 齐全 |
| R11 | 同一操作,小压缩包(成员名是相对路径 www/frpc/…),选中子目录 | ✅ 正常落地为 <目标>/www/frpc/… ⇒ 说明「按选中条目解压」这条代码路径本身是通的,差别只在成员名是否带开头「/」 |
| R12 | 对 R10 那次解压做 strace -f -e trace=file | 无外部 tar/unar 子进程,只有 chdir(<目标目录>) ⇒ 确认走 libarchive 插件、无临时目录搬移环节 |
5.3 归档本身的形态
tar -tzf 与 python3 tarfile 逐条核对:全部 3846 个成员都是绝对路径(/home/x99/backupGhost.sh、/www、/www/frpc/docker-compose.yml 等,属主 uid/gid = 1000 / x99)。这类包一般由 tar czf x.tgz /www /home/x99(带前导「/」)打包产生。
附:本笔记第一版曾把成因判成「覆盖确认对话框弹在 Ark 窗口」——该结论已作废,原因是当时只测了「目标已存在同名文件」的小包场景;本次用同一个 336 MB 的包做「选中 vs 不选」对照,才把差异锁定到「成员名前导 / 」。
解决记录
6. 判据(10 秒自证)
tar -tzf /s500data/fileJob/1606atibm/2105oracle/x99/bak/x99_20260829145835.tar.gz | head
- tar 会提示「从成员名中删除开头的“/”」⇒ 有这个提示就命中本 bug。
- 或用「同一个包」两步对照:拖包内某个目录 → 零落地;解压所有 / 整包拖 → 正常。
7. 处置(按代价从低到高)
- 直接绕过(推荐):这个包要用「解压所有」或「解压到…」;或在终端
tar -xzf <包> -C <目标目录>(GNU tar 自己会剥掉开头「/」,行为正确)。 - 想保留拖拽习惯:先把这个包洗成相对路径成员名再拖:
mkdir x && tar -xzf old.tgz -C x && tar -czf new.tgz -C x .(新包成员是./www/...,Ark 侧能匹配;代码里「./」有显式剥离逻辑,但该形态尚未在 Ark 里实测,用前先拿小包试一次)。 - 要根治拖拽:只能给 Ark 打补丁(
extractFiles()两侧做同样的前导「/」归一化,或用规范化后的路径比较)。未做:需重新编译 Ark;且本机 bugs.kde.org / invent.kde.org TLS 不可达、KDE/ark 在 GitHub 上没有 issue 库 ⇒ 无法确认上游是否已修。
8. 待实测
- 该 bug 是 26.04.3 独有还是长期存在(需逐版本对比
libarchiveplugin.cpp这段代码)。 - 上游是否已修(bugs.kde.org / invent.kde.org 本机不可达)。
- 触发条件是否只有「成员名以「/」开头」这一种(
./www/…形态未在 Ark 里实测)。
9. 取证手法(下次同类问题直接复用)
dbus-monitor "type='method_call',interface='org.kde.ark.DndExtract'"一次就能钉死「Dolphin 到底传了什么目标目录」。- 目标目录用 50 ms 全树轮询 +
/proc/<ark pid>/fd双证据;只看ls会漏掉「建了又删」。 strace -f -e trace=file -s 300 -- .ark-wrapped <包>能直接判「有没有起外部进程」「chdir 到哪」「写了哪些路径」。本机 KDE release 构建下QT_LOGGING_RULES='ark.debug=true'零输出,别指望 qCDebug。- 免鼠标验证「Ark 解压落地」这半边:
qdbus org.kde.ark-<pid> /DndExtract/1 org.kde.ark.DndExtract.extractSelectedFilesTo <目录>;服务名必须按pgrep -x ark的真实 pid 取(qdbus | grep取第一个会打到用户自己那个实例)。 - 判「文件是不是这次写的」看 ctime:
stat -c '%n mtime=%y ctime=%z'(KIO 复制与同盘 rename 都保留 mtime、只更新 ctime)。 - 本机
xdotool不存在;computer_use往 Ark 发合成点击/按键经常不生效、甚至把 Ark 弄崩(本次两次 SIGSEGV)⇒ 别把「自动化选中失败」当成机制失效。 pkill -f <模式>会连执行它的自己一起杀(本次又栽一次,整条命令被 SIGTERM)⇒ 用 pid kill。
10. 落点
- 项目台账(Hermes 维护):
hermes-nixos-workspace/docs/ark-drag-drop-extract-conflict.md(第二版,含全部证据与对照实验);INDEX 检索行与变更日志已同步。 - 可复用排障知识:skill
nixos-desktop-troubleshooting的references/ark-dnd-extract.md(机制 / 静默失败分支 / 取证三件套)。
补充:为什么命令行 tar 没事、Ark 却翻车
一句话:tar 的「安全」发生在写盘那一刻;Ark 的失败发生在写盘之前的「按名单挑条目」。 两层不是同一个地方,所以「tar 能安全处理绝对路径」和「Ark 拖拽解不出来」可以同时成立,互不矛盾。
11.1 采信(已用上游一手材料在本机核实)
| # | 外部说法 | 核实结果 |
|---|---|---|
| R13 | GNU tar 解压时默认剥掉成员名前导「/」,把 /etc/passwd 当 etc/passwd 解到当前目录,所以带绝对路径的包在命令行下完全正常且安全 | ✅ 采信。本机对这个包的 tar -tzf 直接打出「从成员名中删除开头的“/”」,且 `tar -xzf` 行为正确 |
| R14 | 存在安全缺陷 CVE-2024-57966:24.12.0 之前的 Ark 会把条目解到绝对路径 | ✅ 采信。NVD 原文:「libarchiveplugin.cpp in KDE ark before 24.12.0 can extract to an absolute path from an archive.」(published 2025-02-03) |
| R15 | 修复提交 fe518d81 要求 libarchive 把绝对路径当相对路径,并设置 ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS | ✅ 采信。KDE/ark commit fe518d81(2024-11-07)标题即「Treat absolute paths as relative paths during extraction」,正文含该 flag |
| R16 | 本机 Ark 26.04.3 已包含该修复 | ✅ 采信。本机插件源码的写盘 flags 为 ARCHIVE_EXTRACT_TIME | …_SECURE_NOABSOLUTEPATHS | …_SECURE_NODOTDOT | …_SECURE_SYMLINKS ⇒ 写盘这一层不会再跑到绝对路径去 |
11.2 修正(外部说法与代码事实的差别)
- 失败点不在写盘、也不只是「截断路径」那一步,而在抽取前的名单匹配:模型里合成出来的目录条目是相对路径(
parentFor()把fullPath去掉尾斜杠后按「/」分词、跳过空段再重建,根级得到www/),而真实文件条目原样保留绝对路径(emitEntryFromArchiveEntry()直接存/www/frpc/a.txt)⇒ 同一棵树里两种写法混用。 extractFiles()又先剥掉条目名开头的「/」,再拿去和带「/」的模型路径做全等比较 ⇒ 必然对不上 ⇒ 每条continue。表现是静默 0 个文件,而不是「解到绝对路径去」。- 因此「已修复 CVE,但拖拽这条路径仍会以另一种形式翻车」这个方向是对的;但不能理解为安全漏洞复发:
NOABSOLUTEPATHS管的是写盘落点,管不到 Ark 自己那套「模型路径 ↔ 抽取名单」的比较逻辑。 - 结论措辞上也不宜写成「已知的、已修复的缺陷」:本次能确证的是 CVE 与其修复,以及当前版本仍会静默失败这两件事;「同一 bug 号」没有证据。
11.3 待核 / 未核实
- 「
parentFor()返回的路径不带尾部斜杠,导致非顶层条目截断后开头多出一个「/」」这一细节:我读到的parentFor()是fullPath.endsWith('/') → chopped(1)再分词重建相对路径,真正的字符串截断在extractFiles()按rootNode移除那一段。两者是否等价未逐一验证,暂记「方向一致、细节待核」。 - 除 CVE-2024-57966 外是否存在单独的上游 issue 号专记「拖拽抽取静默失败」:本机 bugs.kde.org / invent.kde.org TLS 不可达,GitHub 上的 KDE/ark 只是镜像、没有 issue 库 ⇒ 未核实,对外材料里不要写「已知 bug #xxxx」。
复核命令(本机可直接跑,出网需带 http_proxy=127.0.0.1:7890):tar -tzf <包> | head;grep -n 'ARCHIVE_EXTRACT' <libarchiveplugin.cpp>;curl https://api.github.com/repos/KDE/ark/commits/fe518d81;curl 'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2024-57966'。