Skip to content

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

Closed
WeiLinCool wants to merge 1 commit into
T-Auto:mainfrom
WeiLinCool:fix/xattr-restore-before-mode
Closed

WeiLinCool wants to merge 1 commit into
T-Auto:mainfrom
WeiLinCool:fix/xattr-restore-before-mode

Conversation

@WeiLinCool

@WeiLinCool WeiLinCool commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #324

What was wrong

ops_replace 的发布是"捕获旧 inode 元数据 → 写新 inode → rename",而 PreservedMetadata::restore 的顺序是 chown → fchmod(最终模式)→ ACL → xattrs。当最终模式是 0400 时,替换文件在 fchmod 之后已经不可写,末尾的 fsetxattr 返回 EACCES。

macOS 26+ 会给每个新建文件自动打上 com.apple.provenance,所以在 macOS 27.0.1 上这个缺陷对 0400 必现;同一顺序在任何平台都会静默丢掉目标携带的 user.* 属性,只是别的系统没有这个"人人都有"的属性把它暴露出来:

$ 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))',
       +   'extended attribute user.dsh-ops was not restored (Permission denied (os error 13))'
       + ]

其余五种模式通过,说明不是权限模型的问题,而是恢复顺序。

The change

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

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

基于 v0.4.1(a9ff940)重做。#325 携带同一诊断与修复,但当前与 main 冲突;本 PR 将其重放到当前主干,原始诊断与修复归 @Richeir。

Validation

行为级 A/B(同一主机、同一工具链;撤掉实现即红):

$ git checkout -- native/crates/ops-files/src/file.rs && npm run build:ptc
$ 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
  dsh-ops file-metadata: 1 of 6 check(s) failed

$ git apply <fix> && npm run build:ptc
$ node test/run.mjs file-metadata
  dsh-ops file-metadata: all 6 checks passed

完整门禁(macOS arm64,Rust 1.97.1):

$ node scripts/verify-gate.mjs --keep-going --skip-install
  ok   versions
  ok   cargo version
  ok   build the plugin
  ok   native worker unit tests
  ok   build and stage the native worker
  ok   manifest contract
  ok   full suite verification
  ok   staged runtime closure
  ok   pack the release artifact
  ok   unpack the release artifact
  ok   packed runtime closure
  124 suites passed; skipped: tui-host, tui-native (allowed: mount, tui-host, tui-native)
  manifest: 73/59 checks

darwin-arm64: gate green

$ DSH_OPS_ALLOW_SKIPS=tui-host,tui-native DSH_OPS_REQUIRE_RUNTIME=1 npm run verify
  dsh-ops test: 124 suite(s) passed, 2 skipped in 41s

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

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

CI(由本 PR 的 pull_request 事件触发):

https://github.com/T-Auto/dsh-ops/actions/runs/39534604876 (event: pull_request; 当前 action_required,等待维护者批准首次 fork 运行)

Environment

  • macOS 27.0.1 (26A434),arm64(Apple Silicon)
  • Node v22.22.3 / npm 10.9.7
  • Rust 1.97.1(rustc 1.97.1 / cargo 1.97.1,发布固定版本)
  • 所基于的上游 main:a9ff940(v0.4.1)
  • 安装通道:源码检出(npm ci --ignore-scripts);worker 由 npm run build:ptc 本地构建

)

A read-only replacement is no longer writable when the final mode is applied, so a 0400 target lost every captured xattr. Apply the attributes while the replacement is still writable; the mode stays after chown and the ACL last. The mode matrix now seeds a user attribute and asserts it comes back, so the order is covered on every host rather than only through macOS's com.apple.provenance.
@WeiLinCool

Copy link
Copy Markdown
Contributor Author

按 贡献指南 的"先 issue,后 PR"流程,PR 必须关联 issue 并先在 issue 认领,以防止重复。#324 已指派给 @Richeir,且 #325 是在途实现;本 PR 属于重复提交,先关闭,不占用评审带宽。

重做到 v0.4.1 的工作保留在 fork 分支 WeiLinCool:fix/xattr-restore-before-mode(commit 8914cc4,3 文件 +32/−5);本机 macOS arm64 + Rust 1.97.1 门禁为 darwin-arm64: gate green(124 passed / 2 allowed skips),A/B 红绿与完整读数见本 PR 正文。若 @Richeir 或维护者需要,可直接取用该提交。

(fork 内镜像 PR 已关闭:GitHub 对新 fork 默认不启用 workflow。)

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 只读文件上把已捕获的扩展属性报告为未恢复

1 participant