Skip to content

feat(storage-analyzer): 增加 Linux 支持 - #50

Open
Mizoreww wants to merge 1 commit into
KKKKhazix:mainfrom
Mizoreww:feat/linux-support
Open

feat(storage-analyzer): 增加 Linux 支持#50
Mizoreww wants to merge 1 commit into
KKKKhazix:mainfrom
Mizoreww:feat/linux-support

Conversation

@Mizoreww

Copy link
Copy Markdown

摘要

storage-analyzer 增加 Linux 支持,并在 Ubuntu 24.04 上完成实测。设计上保持三平台对称、纯标准库、安全模型不削弱。

为什么 Linux 不能照抄 macOS 分支

适配过程中撞到三个 Linux 独有的失效模式,它们不是"换个路径表"能解决的:

1. du 对 root 目录是静默低估

macOS 上 du 读不到会报错,能标 denied;Linux 上它把读不到的算作 0 并返回成功/var/lib/docker 动辄几十 GB 却完全不出现在结果里,报告会理直气壮地指错大户。

对策是 reconcile_linux():扫完拿 df 已用减去已归类之和,差额(gap_kb)显式报出来,归入蓝色「系统及其他」并提示用 sudo 核实。

2. 硬链接缓存会导致重复计数

du 只在单次调用内按 inode 去重。uv / pnpm / conda / nix 用硬链接把同一份内容挂在多处,分次 du 各算一遍。本机实测合计 548.3 GB 超出实际已用 537 GB —— 差 11.4 GB。

不处理的话,报告会向用户承诺一个虚高的可释放空间。现在这种情况会输出 overlap_kb,要求报告注明"实际释放量可能更小",并优先推荐 uv cache prune(只清无引用条目)而非 uv cache clean

3. 废纸篓不是一个目录,是一份规范

XDG Trash 要求同时写 files/<name>info/<name>.trashinfo(含原路径与删除时间)。只 move 到 files/ 是残废的废纸篓 —— 文件管理器里点"还原"会失败。

实现为三级回退:gio trashtrash-put → 纯标准库手写 .trashinfo。第三级保证零依赖环境下功能不缺失。

跨文件系统一律拒绝:规范要求写到目标盘自己的 .Trash-$UID,而 shutil.move 跨设备会退化成"复制 + 删源" —— 对一个 97 GB 的目录意味着先复制 97 GB(盘可能都不够),语义上是慢速删除而非"移到废纸篓"。宁可不给按钮,也不做用户以为可逆、实际不可逆的事。

其他改动

du -x 全局启用(影响 macOS)—— 这是 bug 修复而非 Linux 特供:du 默认跨挂载点,若 ~/data 是外置盘挂载点,扫 $HOME 会把整块外置盘算进主盘占用。macOS 上同样会错。

sudo 项只读展示 —— /var/lib/docker/var/log、apt/pacman/dnf 缓存等 root 大户照常上灯、照常给命令,但 trash_paths 一律留空,标题带 [需 sudo]。这不是铁律的例外,而是"删除命令只展示、用户自己在终端确认后运行"的标准形态。删除通道对它们结构性关闭(白名单构造时就进不去,不靠文档约定)。

兜底下钻 —— 固定目标表列不全各发行版的目录,所以对 $HOME/var 各做一层通用下钻,捞出表外的大目录。与缺口对账互补:对账说"差了多少"(数字),下钻说"差在哪"(路径)。

无头降级 —— 无 DISPLAY/WAYLAND_DISPLAY 时不弹浏览器,打印 ssh -L 转发提示;SKILL.md 指示 agent 跳过 Step 3,直接在对话里给完整诊断。服务器上"/ 分区满了"是高频场景,产出一个要 scp 才能看的 HTML 文件方向反了。

多挂载点 —— system.disks 收录所有本地文件系统(滤掉 tmpfs / squashfs / 网络盘),复用 Windows 分支已有的多盘列表渲染,模板无需改动。

实测(Ubuntu 24.04.4 / ext4 / GNOME)

  • 扫描 39 秒,正确识别 537 GB 已用、2 个有效挂载点
  • 缺口对账抓出 11.4 GB 硬链接重叠
  • XDG 废纸篓:标准库写入 → gio trash --list 可见 → --restore 还原到原路径,闭环通过
  • 跨文件系统拒绝:用 /dev/shm(不同 st_dev)验证,明确拒绝且源目录完好
  • trash-put 回退级:屏蔽 gio + 注入假二进制,验证调用契约(带 -- 终止符防路径被当选项)
  • 无头降级:env -u DISPLAY -u WAYLAND_DISPLAY 验证,不弹浏览器、ssh -L 端口正确
  • 安全护栏(真实 HTTP 请求):坏 token / 白名单外路径 / 橙灯试 rm / sudo 项试 trash 全部 403;绿灯正常入废纸篓并还原

未覆盖的形态

诚实说明测试边界:

  • 仅在 Ubuntu + GNOME + 单盘 + 桌面环境验证。Arch / Fedora / NixOS、多盘、真实无头服务器未测
  • LINUX_SUDO_TARGETS 里 pacman / dnf / nix 的路径是按文档写的,未在对应发行版验证
  • 本机未装 Docker,因此 gap_kb > 0 的分支(root 目录静默低估)未被真实数据触发 —— 这是本次改动中论证最充分但实测最薄弱的一环
  • Windows 分支仍是上游原状,本 PR 未触碰

文件

文件 改动
scripts/scan.py +470 —— scan_linux / list_mounts_linux / sudo_targets_linux / uncategorized_linux / reconcile_linux(逐文件系统对账)/ system_info_linux / dev_caches_linux / du_kb(判读退出码与 stderr);du_children 显式比对 st_dev 识别挂载点
scripts/server.py +471 —— _trash_linux 三级回退、_trash_xdg_stdlibO_EXCL 原子占位)、_xdg_trash_dir_same_fs_as_trash_mountpoints/_crosses_mount_rmtree_at(fd 相对迭代删除)、has_guixdg-open 分支、按操作类型分档的护栏、stdout flush=True
references/linux.md 新建 —— XDG 布局、conda/HF/docker/snap/flatpak 辨认与分级、sudo 清单、三条 Linux 独有约束
SKILL.md description 加 Linux 与 No space left on device / inode 两个触发词;Step 1/2/3 补 Linux 分支;平台状态标注已实测
scripts/build_report.py +4 —— 打开命令按平台输出 open / start / xdg-open

assets/report_template.html 未改动。

代码审查过程

本 PR 经过四轮对抗式代码审查(由 Codex 以独立视角执行),累计发现并修复 8 个 High26 个 Medium/Low。值得说明的是,其中 3 个 High 是前一轮修复本身引入的新缺陷

轮次 High 发现
1 _trash_windows() 被编辑破坏(Windows 废纸篓崩溃);护栏对破坏性操作放宽到 $HOME 外;并发废纸篓静默覆盖文件;对账混用文件系统
2 rp == HOME 可使 rmtree 抹除整个家目录 —— 修复第 1 轮时引入
3 校验与执行分离产生路径交换窗口 —— 修复第 2 轮时引入
4 同设备 bind mount 可穿透 home 边界;身份复核可被绕过(保留叶子 inode、替换父目录)

第 4 轮的 bind mount 问题源于我在第 3 轮以「/proc/self/mountinfo 解析成本过高」为由驳回了该建议 —— 实测证明解析只需一次 split(),该判断是错的,现已采纳。

第 5 轮审查未能完成:审查工具连续四次未产出结论(失败模式各异),因此本 PR 最危险的一段代码(_rmtree_at 从递归改为显式栈迭代)仅经过作者自测,未获独立审查。自测覆盖:600/1200 层深目录树(递归版在此崩溃并留下半删状态)、50×20 宽深树的 fd 泄漏检查(零泄漏)、权限受限子目录、fifo/socket/悬空符号链接/设备节点等特殊文件、/etc/passwd 未被跟随删除。

安全模型的当前状态

破坏性操作(rm / trash)现有五道独立防线,每道都由代码强制而非文档约定:

  1. 白名单:仅绿灯/橙灯 trash_paths 中的 realpath 可进入
  2. 准入过滤needs_sudo 项、$HOME 本身、跨文件系统路径、bind mount 一律排除,且同步从渲染数据中剔除(避免死按钮)
  3. 越界检查:破坏性档只认 $HOME 的严格子孙
  4. 挂载穿透检查/proc/self/mountinfo 逐级校验祖先
  5. inode 锚定:校验时持有父目录 fd,执行前复核 (st_dev, st_ino),删除全程 fd 相对操作

批量请求先全量校验再执行,任一路径非法即整批拒绝且不做任何删除。

扫描、分级、报告、一键删除四条链路全部支持 Linux,保持三平台对称与
纯标准库约束,安全模型不削弱。

三个 Linux 独有的失效模式需要专门处理:

- du 对 root 目录静默低估(读不到算作 0 并返回成功),因此新增按文件系统
  分别进行的缺口对账,把差额如实报出而不是让报告指错大户
- 硬链接缓存(uv/pnpm/conda/nix)在多次 du 中被重复计数,实测本机超出
  实际已用 11.4 GB,现输出 overlap 并要求报告注明实际释放量更小
- XDG Trash 需同时写 files/ 与 info/*.trashinfo 才能被文件管理器还原,
  实现为 gio → trash-put → 标准库三级回退;跨文件系统一律拒绝,
  不做"复制+删源"式的伪废纸篓

顺带修复一处影响 macOS 的既有缺陷:du 未加 -x 会把外置盘挂载点的体量
算进主盘。

破坏性操作新增五道由代码强制的防线:白名单、准入过滤(needs_sudo /
$HOME 本身 / 跨文件系统 / bind mount)、严格子孙检查、mountinfo 挂载
穿透检查、父目录 fd 锚定与 inode 复核。批量请求先全量校验再执行。

实测于 Ubuntu 24.04.4 / ext4 / GNOME。经四轮对抗式代码审查,修复
8 个 High、26 个 Medium/Low;第五轮未能完成,详见 PR 描述。
@Mizoreww
Mizoreww force-pushed the feat/linux-support branch from 576fa77 to 7455363 Compare July 29, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant