Skip to content

fix(file): restore extended attributes before the final mode - #325

Merged
T-Auto merged 2 commits into
T-Auto:mainfrom
Richeir:fix/restore-xattrs-before-mode
Oct 21, 2026
Merged

T-Auto merged 2 commits into
T-Auto:mainfrom
Richeir:fix/restore-xattrs-before-mode

Conversation

@Richeir

@Richeir Richeir commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #324

What was wrong

ops_replace 的发布方式是写入新 inode 再 rename 覆盖目标,因此它先捕获旧 inode 的元数据、之后再重新应用。PreservedMetadata::restore 的应用顺序是 chown → fchmod(最终模式)→ ACL → xattrs,而替换文件一旦被 fchmod 成 0400 就不再可写,末尾的 setxattr 因此返回 EACCES。

test/file-metadata.test.mjs 把它表现为一次"干净替换"上不该出现的警告:

$ node test/run.mjs file-metadata
  FAIL every mode a caller can set survives a replacement
       AssertionError [ERR_ASSERTION]: a clean restore must not carry a warning
       + [
       +   'extended attribute com.apple.provenance was not restored (Permission denied (os error 13))'
       + ]
       - undefined

六种模式里只有 0400 失败(绕过套件、直接对已构建的 worker 逐个跑):

0600 -> null      xattrs: ["com.apple.provenance"]
0400 -> ["extended attribute com.apple.provenance was not restored (Permission denied (os error 13))"]  xattrs: []
0640 -> null      xattrs: ["com.apple.provenance"]
0644 -> null      xattrs: ["com.apple.provenance"]
0755 -> null      xattrs: ["com.apple.provenance"]
0750 -> null      xattrs: ["com.apple.provenance"]

根因用独立的 libc 探针确认过:模式 0400 下,对替换文件的 fd 调用 fsetxattr 与基于路径的 setxattr 都返回 Permission denied (os error 13)——即使写入与现有值完全相同的内容。

macOS 26+ 会给每个新建文件打上 com.apple.provenance,因此在这些系统上 0400 替换的警告是无条件的。同一顺序在任何平台上都会静默丢掉源文件携带的 user.* 属性,只是那些平台没有这个"人人都有"的属性来把它暴露出来。

复现:npm run build:ptc && node test/run.mjs file-metadata。

The change

  • native/crates/ops-files/src/file.rs:扩展属性改在替换文件仍可写时应用;模式仍放在 chown 之后(成功的 chown 会清除 setuid/setgid);ACL 仍放在最后(chmod 会重写 ACL 的 mask 项)。
  • test/file-metadata.test.mjs:模式矩阵在最终 chmod 之前挂一个 user.dsh-ops 属性,并断言它被原样恢复,使该顺序在所有平台都受断言约束,而不只依赖 macOS 免费提供的属性。
  • docs/CHANGELOG.md:## [Unreleased] 双语条目。

刻意没做:严重性分级一字未动——模式仍是"不可恢复即失败、不发布任何东西",ACL 仍是"不可恢复即失败",属主与其余扩展属性仍是尽力而为并在 metadataWarnings 里报告。本次只移动顺序,不改变哪一项是可选的。

Validation

  • 本 PR 有一次 pull_request 事件触发的 CI required run:fork PR,未停在 action_required,聚合检查 CI required 已 success;URL 见下方代码块
  • npm run verify 的实际输出(见下方代码块)
  • 撤掉本次实现后,相关检查会变红(行为级红绿,见下方对照)
  • docs/CHANGELOG.md 的 ## [Unreleased] 已加条目
  • 行为变化已同步相关 docs(未改动面向模型的描述或参数,schema 与提示词字节无变化)
  • 未新增依赖,未改 package.json 的版本与 optionalDependencies

撤销验证:把 file.rs 的恢复顺序改回原样并重跑 npm run build:ptc,同一套件立刻变红:

$ node test/run.mjs file-metadata
dsh-ops file-metadata: 1 of 6 check(s) failed
--- file-metadata.test.mjs exited 1

修复后(本 PR HEAD 9249d2a):

$ node test/run.mjs file-metadata
dsh-ops file-metadata: all 6 checks passed
dsh-ops test: 1 suite(s) passed in 0s

$ cargo test --workspace --locked --manifest-path native/Cargo.toml
test result: ok. 37 passed; 0 failed; 0 ignored (19 result lines across the workspace)

$ npm run verify
dsh-ops test: 124 suite(s) passed, 2 skipped in 115s

$ npm run check:size
check-file-size: 382 code file(s) checked, all at or under 1000 lines (longest: bench/setup/profile.mjs (924))

2 个跳过是 TUI 套件,需要 DSH_OPS_TUI_ROOT 指向 dsh-TUI 检出,与本改动无关。

$ node test/run.mjs file-metadata
dsh-ops file-metadata: all 6 checks passed
dsh-ops test: 1 suite(s) passed in 0s

$ gh run view 39394238056 --json url,event,status,conclusion --jq '"\(.url) \(.event) \(.status) \(.conclusion)"'
https://github.com/T-Auto/dsh-ops/actions/runs/39394238056 pull_request completed success

$ gh run view 39394238056 --json jobs --jq '.jobs[] | "\(.name): \(.status)/\(.conclusion)"'
source-size: completed/success
node-floor: completed/success
validate (macos-latest, darwin-arm64): completed/success
validate (ubuntu-24.04, linux-x64): completed/success
validate (windows-latest, win32-x64): completed/success
workflow-lint: completed/success
tools-trial: completed/success
bench-smoke: completed/skipped
CI required: completed/success

Environment

  • macOS arm64(Apple Silicon),macOS 27.0.1(内部版本 26A434)
  • Node v26.12.0,npm 11.20.0
  • 所基于的上游 main commit:8222631
  • 安装通道:源码检出(npm ci --ignore-scripts);native worker 由 npm run build:ptc 本地构建
  • Rust:1.97.1(rustup 默认工具链,与 CI 及 docs/CONTRIBUTING.md 固定的版本一致),cargo 1.97.1 / rustc 1.97.1

)

A read-only replacement (0400) denies setxattr, so restoring the captured
attributes after fchmod reported every one of them as unrestored. On macOS
that covered com.apple.provenance, which the system gives every new file, so
a clean 0400 replace always carried a warning.

Apply the attributes while the replacement is still writable, then the mode
(after chown, which clears setuid and setgid), then the ACL (which chmod
rewrites).

The mode matrix now attaches a user attribute before the final chmod, so the
ordering is asserted on every host rather than only where macOS supplies the
attribute for free.
@Richeir Richeir changed the title fix(file): restore extended attributes before the final mode [Draft] fix(file): restore extended attributes before the final mode Oct 20, 2026
Thatgfsj
Thatgfsj previously approved these changes Oct 20, 2026

@Thatgfsj Thatgfsj left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

除了ops-files里面修改的是与之前的测试有关的内容,简单查看并无问题,仅仅新加函数+拆分函数,别的内容均不影响主功能

@WeiLinCool

Copy link
Copy Markdown

#325 的修复与当前 main 冲突(crate 拆分与 CHANGELOG 变动)。我把同一改动重做到 v0.4.1 上并跑通了 macOS 门禁:fork 分支 WeiLinCool:fix/xattr-restore-before-mode,commit 8914cc4(3 文件 +32/−5),darwin-arm64: gate green(124 passed / 2 allowed skips),撤掉实现后 file-metadata 立即变红。需要的话可直接 cherry-pick 这个提交;重复的 #350 已按贡献指南关闭。

@T-Auto
T-Auto merged commit 77bf4e3 into T-Auto:main Oct 21, 2026
9 checks passed
@Richeir Richeir changed the title [Draft] fix(file): restore extended attributes before the final mode fix(file): restore extended attributes before the final mode Oct 21, 2026
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.

[BUG][Low] ops_replace 在 0400 只读文件上把已捕获的扩展属性报告为未恢复

4 participants