Repository navigation
B2:Linux 原生 Sandbox(立在 B1 上) - #524
jiangkoumo wants to merge 3 commits into
Conversation
…der (Area B1) Extract the complete Sandbox runtime interface and shared native/node plumbing from 9aa10e6 onto upstream/main (35d3657), implementing the Docker backend and leaving same-signature Unsupported slots for backend/{linux,macos,namespaces}/ so B2, B3 and Ephemeral-AI-Lab#515 only replace their own backend directories. In layerfs-sandbox: - Define concrete Runtime (Docker, MacosNative, LinuxNative, Namespaces, Windows) and Sandbox (Docker, Native, Namespaces) enums in backend/runtime.rs. - Add provider capabilities (including LoopbackNfs, MicroVm, RemoteCloud), LaunchPhase/LaunchFailure, ProviderLaunchOptions (Local, Namespaces, Docker) and SandboxRecord (Local, Namespaces, Docker). - Add types::Image and cross-backend ExecRequest; move shared json and strings helpers to backend/. - Implement DockerProvider and DockerInstance over existing Docker Engine lifecycle and streaming execution. - Include shared backend/native/* lifecycle, identity, listener and streams modules under cfg(any(linux, macos)), with same-signature Unsupported stubs in backend/{linux,macos,namespaces}/. In layerfs-api: - Route SandboxApi through Arc<Runtime> via with_runtime and create_provider_file, retaining local resource ownership and command identity validation. - Share TCP/Noise/Hello and Store install pipeline in sandbox/connection.rs. In layerfs-node and @layerfs/agent: - Take verbatim 9aa10e6 shared binding modules (local, discovery, runtime, sandbox, workspace, exec, registry, lib) with a no-op unmount slot for B3. - Expose deploy.sandbox, deploy.localDir, deploy.sandboxBackend and deploy.imageArchive settings in @layerfs/agent. Production LOC: 102831 -> 106022 (delta +3191) First-parent comparison: upstream/main 35d3657 (tree ddea2b5) -> committed tree ae48e29. Scopes: - core: 38395 -> 38395 (delta 0) - engine: 61777 -> 64938 (delta +3161) * layerfs-sandbox: 3417 -> 5526 (delta +2109) * layerfs-api: 1940 -> 2081 (delta +141) * layerfs-node: 2744 -> 3655 (delta +911) - api: 2659 -> 2689 (delta +30) * @layerfs/agent: 849 -> 879 (delta +30) - dsh: 0 -> 0 (delta 0) - reference: absent (retired R9c; last counted 65,417 at tree 498dd19) Method: tools/production_loc.py (blob 3341e4d), --root <exact exported tree> --json for both snapshots; first-party production Rust, runtime SQL and JavaScript; tests, docs, tooling, generated and third-party files excluded.
Production LOC: 106022 -> 106124 (delta +102) First-parent comparison: b1-interface a32da57 (tree ae48e29) -> rehearsal-b2 tree. Scopes: - core: 38395 -> 38395 (delta 0) - engine: 64938 -> 65040 (delta +102; layerfs-sandbox 5526 -> 5628) - api: 2689 -> 2689 (delta 0) - dsh: 0 -> 0 (delta 0) - reference: absent (retired R9c; last counted 65,417 at tree 498dd19) Method: tools/production_loc.py (blob 3341e4d), --root <exact tree> --json.
B2 replaces backend/linux/ with the real Linux native implementation, but linux/mounts.rs::filesystem() has its only caller in the namespaces backend, which lands as its own change. Alone on this branch the function is dead code, and the Linux clippy lane (`cargo clippy --workspace --all-targets -- -D warnings`, which runs for x86_64-unknown-linux-gnu) fails on it, so B2 could not be merged on its own. Allow that one function where it is declared, in a file only B2 owns, so the branch is green on Linux before the namespaces change lands. Nothing about the parsing, the mount table source or the lazy detach changes, and the namespaces change still deletes or keeps the attribute freely because no other patch touches this file. Production LOC: 106124 -> 106125 (delta +1) First-parent comparison: rehearsal-b2 336e296 (tree a85a639) -> committed tree 65a79ec. Scopes: - core: 38395 -> 38395 (delta 0) - engine: 65040 -> 65041 (delta +1) * layerfs-sandbox: 5628 -> 5629 (delta +1) - api: 2689 -> 2689 (delta 0) - dsh: 0 -> 0 (delta 0) - reference: absent (retired R9c; last counted 65,417 at tree 498dd19) Method: tools/production_loc.py (blob 3341e4d), --root <exact exported tree> --json for both snapshots; first-party production Rust, runtime SQL and JavaScript; tests, docs, tooling, generated and third-party files excluded.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
…fs_v2_0_0 B2 (layerfs-connect#46): native Linux Sandbox, filling B1's Linux process and mount-table backend (engine/crates/layerfs-sandbox/src/ backend/linux/) and adding its tests. Merged at PR head 45556f3 onto the B1 merge d39bb6c; no conflicts. One merge adjustment: B2's new native_provider test gives its Docker record `Some(...)` for the volume, which is optional on this branch (#506). Verified on macOS (Rust 1.85.1): workspace warning-denying Clippy, all targets, and format check (only the existing minio.rs drift). layerfs-sandbox tests: 49 passed, 2 failed, 1 ignored. The two failures (local_search_path, native_provider) stop at B1's macOS placeholder "macOS native process and mount table" (Unsupported), which B3 (#525) replaces; B2's own head has the same placeholder, so they cannot pass on macOS before B3. layerfs-node's tests are Linux-only. Not run: Linux build and tests, where B2's code runs (no Linux C toolchain on the host, Docker not running). Disposable only; Durable NOT_RUN, disabled by owner. Production LOC: 115107 -> 115210 (delta +103) First-parent comparison: layerfs_v2_0_0 d39bb6c (tree cae5bb4) to prepared merge tree c17f04f. Scopes: core 38400 -> 38400 (+0), engine 73412 -> 73515 (+103), api 3295 -> 3295 (+0). By crate: layerfs-sandbox +103. Method: tools/production_loc.py (blob 686d269) --root <exported tree> --json for both snapshots; first-party production Rust, runtime SQL and JavaScript; tests, docs, tooling, generated and third-party files excluded. Reference absent (retired R9c; last counted 65,417 at tree 498dd19). Deleted tests: none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@jiangkoumo Merged into 已合入 |
1. 这个 PR 在做什么(一句话)
让 LayerFS 在 Linux 上用「本机普通进程」跑 Sandbox:把 Sandbox 的 daemon 拉起成一个本机进程,记下它的 pid 与启动时刻(用来识别进程是否还活着、以及 pid 被复用的情况),命令以指定 uid/gid 身份在指定目录执行,停止时按「先停子进程组、再惰性卸载残留挂载点」的次序收拾干净。
这些能力在 Mac 上是同一份代码,区别只在「问内核」的那几行:
/proc/<pid>/stat+ 系统 boot idproc_pidinfo/sysctl/proc/sys/kernel/random/boot_idsysctl的kern.boottime(在 B3 里)/proc/self/mountinfo+umount2(MNT_DETACH)mount表 +umount -f本 PR 只填「Linux 那几行的实现」:
engine/crates/layerfs-sandbox/src/backend/linux/这一个目录,加上 4 个测试文件。2. 为什么这个 PR 这么小(统一口径)
原来 #402 / #498 是一个大 PR,把「接口 + 三个后端 + VM + JS」全放在一起,评审根本读不完,几个 PR 又互相踩同一批文件,合并时冲突几十个 hunk。所以拆成四个:
Runtime/Sandbox/provider 选项与能力、Docker 后端、Linux 与 macOS 共用的本地进程实现(engine/crates/layerfs-sandbox/src/backend/native/*约 1230 行),以及layerfs-node侧本地部署接入(src/local.rs、runtime.rs、discovery.rs等)main换句话说:平台无关的部分已经在 B1 里了。 本 PR 剩下的只有「问 Linux 内核」的那几行代码,所以它小不是省事,而是该在别处的代码本来就不在这里。
这种切法带来三个已经实测过的性质:
9aa10e65c逐字节一致——拆的是提交,不是代码。-D warnings),不必等别的 PR 先合。3. 改了什么
相对 B1 一共 6 个文件(
+1099 / −15):engine/crates/layerfs-sandbox/src/backend/linux/mounts.rs/proc/self/mountinfo(含八进制转义解码)、按深度倒序列出目录下的挂载点、umount2(MNT_DETACH)惰性卸载后复查;filesystem()查某个路径所在的文件系统类型(见 §3.1)engine/crates/layerfs-sandbox/src/backend/linux/process.rs/proc/<pid>/stat的进程启动时刻、读 boot id,据此判断「还是那个进程」而不只是「pid 存在」engine/crates/layerfs-sandbox/tests/native_provider.rsRuntime::LinuxNative:launch → reopen → 停止 → 删除;启动失败时保留部分资源并精确归还;非法名字与错后端选项在Validate阶段拒绝;Debug 输出不得泄露 setup 密文engine/crates/layerfs-sandbox/tests/local_runtime.rsengine/crates/layerfs-sandbox/tests/local_search_path.rs#![cfg(target_os = "macos")];为了让 B2 与 B3 携带的测试文件逐字一致而随本 PR 走,在 Linux 上不产生测试engine/crates/layerfs-sandbox/tests/local_stop_mount.rs#[ignore]:需人工提供一次性挂载点)src/backend/linux/mod.rs在 B1 里已经与最终形态逐字节一致(同签名的Unsupported桩),本 PR 不需要改它。生产代码目录(本 PR 相对 B1;计数器
tools/production_loc.py,提交45556f3db,只列engine/crates/layerfs-sandbox/src/backend/linux/,测试在tests/下不计入):engine/crates/layerfs-sandbox/src/backend/linux/ [133] mod.rs 3 mounts.rs 90 process.rs 40(B1 里这三个桩文件合计 30 行生产代码,所以本 PR 的生产代码增量是 +103。)
3.1 评审会注意到的一行:
#[allow(dead_code)]linux/mounts.rs里的filesystem()唯一的调用方是 #515 的 namespaces 后端(它要确认 overlay 的可写层落在一个能当 overlay upper 的文件系统上)。B2 单独提出来时,这个函数在本分支上没人调用,而 CI 的 Linux clippy 车道用-D warnings,会直接判死代码、让 B2 单独立不住。按最小改动原则,在这个函数的声明处加一行
#[allow(dead_code)](附带两行注释说明原因),并写清楚:这个文件只由 B2 拥有,#515 不碰它,所以不会因为这一行产生冲突。#515 合入后这个属性变成冗余但无害(合并后的树已实测 clippy 仍 0 warning)。4. 怎么验证
4.1 已在 macOS 上跑过(
. ~/.cargo/env,cargo +1.85.1 --locked)cargo +1.85.1 check --locked --workspace --all-targetscargo +1.85.1 clippy --locked --workspace --all-targets -- -D warningscargo +1.85.1 clippy --locked --target x86_64-unknown-linux-gnu -p layerfs-sandbox --all-targets -- -D warningsfunction filesystem is never used,-D dead-code);修后 exit 0python3 -B tools/check_product_boundary.pyPASS: scanned 991 ...,exit 0python3 -B tools/check_fuser_integrity.pypython3 -B -m unittest discover -s tools -p 'test_*.py'OKcargo +1.85.1 fmt --all --checkgit merge --no-ff --no-commit <515分支>macOS 上做不到的一条:在 macOS 上把整个 workspace 交叉编译到
x86_64-unknown-linux-gnu会被 C 交叉工具链拦住(libsqlite3-sys等 build script 报failed to find tool "x86_64-linux-gnu-gcc")。CI 的 clippy 车道是在 Linux runner 上跑的整 workspace 形态,所以这一项在 Linux 机器上补跑(见 4.2)。4.2 需要在 Linux 机器上跑的(真机验证)
local_runtime源码注明不需要 root(root 身份下命令身份退化为nobody),请两种身份都跑。native_provider的 Linux 分支必须 root:它要真的 setuid 到 uid 501/gid 20 才能验证「命令以指定身份执行」。非 root 下该测试只走「NativeProvider::new必须失败」的早退分支——这本身就说明「非 root 不能启用 Linux 原生 Sandbox」的门禁。linux/mounts.rs的detach()(/proc/self/mountinfo解析 +umount2(MNT_DETACH)+ 复查)在local_runtime的 stop/delete 里被真实执行,但沙箱目录下没有挂载,所以只走「读取 + 过滤 + 空结果」;真正的umount2成功路径与「卸载后复查失败」路径未被测试覆盖。macOS 侧有一个#[ignore]的local_stop_mount人工证明,Linux 暂无同形测试。如果评审要求,可另开一个需要一次性真实挂载点的人工证明(属新增测试,本 PR 未加)。5. 风险
layerfs-node侧的端到端链路缺一条 Linux 测试:engine/crates/layerfs-node/tests/native_local.rs(#![cfg(target_os = "linux")],来自 feat: macOS 原生适配——daemon 内置 loopback NFSv3 导出,普通用户 mount_nfs 挂载 Workspace(无 Docker / 无 macFUSE / 无 root) #427)按拆分归属随 B3 走,不在本 PR。也就是说,在 B2 合入、B3 未合入的窗口里,「经 N-API 绑定拉起 Linux 原生 Sandbox」这条链路没有自动化测试;B3 合入后自动补齐。这是拆分的已知代价,不是本 PR 的回归。#[allow(dead_code)]是过渡属性:#515合入后它变成冗余(无害)。若后续不想保留,应作为独立提交清理,不要塞进 docs(512): propose a VM Sandbox backend (Sandboxes as namespaces in one VM per Store) #515,否则会破坏「一个文件只有一个 owner、各 PR 0 冲突」的约定。9aa10e65c逐字节一致;正式分支定稿后会再核一次)。如果先合了别的 PR 再合 B2,git 会自动处理,不需要谁去 rebase。native_provider在非 root 下的断言就是这个语义。linux/mounts.rs的detach()在local_runtime的 stop/delete 里会真实执行,但沙箱目录下没有挂载,所以只覆盖「读取 + 过滤 + 空结果」;umount2(MNT_DETACH)成功路径未覆盖(macOS 侧有#[ignore]的local_stop_mount人工证明,Linux 暂无同形测试)。这是「共用行为在 B1、平台细节在各平台」拆分下的已知空白,不影响本 PR 的编译与门禁结论,但属于合并后仍待补的验证。