Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/hand/examples/buildfence.rs

7.5 KiB, 1 run

created by r2519314175:897, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// buildfence -- can a fenced `cargo` compile this repository?
2//
3// Written on 2026-08-25 to settle BLOCKERS B6 with a measurement rather than an
4// argument. The question is whether a daimon can be given a truthful "does this
5// build" through the ordinary `run` door, inside the fence that door applies.
6// Reasoning about it is unreliable in the same way `fenceprobe` is kept for:
7// Landlock's union semantics do not read the way they behave, and cargo's own
8// failure for an unreadable path dependency is a manifest error rather than a
9// permission one, so a reader who has not seen it guesses wrong.
10//
11// cargo run --release --example buildfence --manifest-path hand/Cargo.toml -- <root>
12// cargo run --release --example buildfence --manifest-path hand/Cargo.toml -- <root> --with-fe2o3
13// cargo run --release --example buildfence --manifest-path hand/Cargo.toml -- <root> --with-fe2o3 --deny-history
14//
15// WHAT IT ANSWERED, 2026-08-25, measured on this machine and recorded here so the
16// next reader does not have to spend the build: without fe2o3 the fenced cargo
17// cannot read `fe2o3_core/Cargo.toml` and stops there with Permission denied;
18// with fe2o3 read-only a COLD `cargo check` of the hand compiles four fe2o3
19// crates from source in 12 s; and denying that tree's `.ore` and `.git` inside
20// the same grant costs the build nothing while both histories stay refused.
21//
22// `<root>` is the folder the user granted, and defaults to the working
23// directory. The
24// second form adds the fe2o3 tree read-only, which is the widening the blocker is
25// asking about; the third denies that tree's `.ore` and `.git` inside the same
26// grant, which is the question of whether the widening can be made narrow enough
27// to be worth taking. The three runs together say what each costs.
28use daimond_hand::fence::{Fence, Unfenced};
29use daimond_hand::wire::FenceSpec;
30
31use std::path::PathBuf;
32use std::process::Command;
33
34// Where fe2o3 lives, which every crate in this repository depends on BY PATH.
35// Overridable, because the answer is about the SHAPE of the dependency and not
36// about this machine: set BUILDFENCE_FE2O3 to ask the same question elsewhere.
37const FE2O3_DEFAULT: &str = "/home/jason/usr/code/rust/fe2o3";
38
39fn main() {
40 let args: Vec<String> = std::env::args().skip(1).collect();
41 let with_fe2o3 = args.iter().any(|a| a == "--with-fe2o3");
42 let deny_history = args.iter().any(|a| a == "--deny-history");
43 // A reflux fixture is a whole crate at the granted root, so the question there
44 // is asked of the root itself rather than of `hand/`.
45 let lib_here = args.iter().any(|a| a == "--lib-here");
46 let root = match args.iter().find(|a| !a.starts_with("--")) {
47 Some(r) => r.clone(),
48 None => match std::env::current_dir() {
49 Ok(d) => format!("{}", d.display()),
50 Err(e) => { println!("no root given and no working directory: {}", e); return; },
51 },
52 };
53 let fe2o3 = std::env::var("BUILDFENCE_FE2O3")
54 .unwrap_or_else(|_| FE2O3_DEFAULT.to_string());
55 let home = match std::env::var("HOME") {
56 Ok(h) => h,
57 Err(_) => { println!("no HOME, so no toolkit roots to compute."); return; },
58 };
59 // The rust toolkit's roots, exactly as `Toolkit::grants` names them in the
60 // app and `TOOLKIT_ROOTS` clamps them in the hand.
61 let mut rw: Vec<String> = vec![
62 root.clone(),
63 format!("{}/.cargo/registry", home),
64 format!("{}/.cargo/git", home),
65 format!("{}/.cargo/.package-cache", home),
66 format!("{}/.cache/cargo-targets", home),
67 ];
68 let mut ro: Vec<String> = vec![
69 format!("{}/.cargo/bin", home),
70 format!("{}/.rustup", home),
71 ];
72 let mut deny: Vec<String> = Vec::new();
73 if with_fe2o3 {
74 ro.push(fe2o3.clone());
75 }
76 if deny_history {
77 deny.push(format!("{}/.ore", fe2o3));
78 deny.push(format!("{}/.git", fe2o3));
79 }
80 // A scratch, because `Scratch` gives every real run one and points TMPDIR at
81 // it -- without it a fenced cargo dies part way through on a temp directory.
82 let scratch = format!("{}/.cache/daimond/buildfence-scratch", home);
83 if let Err(e) = std::fs::create_dir_all(&scratch) {
84 println!("could not make a scratch at {}: {}", scratch, e);
85 return;
86 }
87 rw.push(scratch.clone());
88
89 let spec = FenceSpec { rw: rw.clone(), ro: ro.clone(), deny: deny.clone(), net: false };
90 println!("root {}", root);
91 println!("rw {}", rw.join("\n "));
92 println!("ro {}", ro.join("\n "));
93 println!("deny {}", if deny.is_empty() { "(none)".to_string() } else { deny.join("\n ") });
94 println!("net false");
95
96 let f = Fence::detect();
97 let plan = match f.plan(&spec, &Unfenced::Refuse) {
98 Ok(p) => p,
99 Err(e) => { println!("plan failed: {}", e); return; },
100 };
101 match plan.apply() {
102 Ok(_) => println!("--- fence applied ---"),
103 Err(e) => { println!("apply failed: {}", e); return; },
104 }
105
106 // What the widening exposes, asked directly rather than inferred: a source
107 // file of fe2o3, and then the two histories. The signed one holds a key.
108 for probe in [
109 format!("{}/fe2o3_core/Cargo.toml", fe2o3),
110 format!("{}/.ore/config", fe2o3),
111 format!("{}/.git/HEAD", fe2o3),
112 ] {
113 println!("read {:<58} {}", probe,
114 match std::fs::File::open(&probe) { Ok(_) => "PERMITTED", Err(_) => "refused" });
115 }
116
117 // The question, asked of the cheapest thing that has to read every manifest:
118 // `cargo metadata` resolves the graph and nothing else, so a refusal here is
119 // about READING the dependency and not about compiling it.
120 let jobs = match lib_here {
121 true => vec![("check --lib, at the root", vec!["check", "--offline", "--lib",
122 "--message-format", "short"])],
123 false => vec![
124 ("metadata, repository root", vec!["metadata", "--offline", "--no-deps",
125 "--format-version", "1", "--manifest-path", "Cargo.toml"]),
126 ("check, the hand", vec!["check", "--offline", "--message-format", "short",
127 "--manifest-path", "hand/Cargo.toml"]),
128 ],
129 };
130 for (what, argv) in jobs {
131 let out = Command::new("cargo")
132 .args(&argv)
133 .current_dir(PathBuf::from(&root))
134 .env_clear()
135 .env("HOME", &home)
136 .env("PATH", format!("{}/.cargo/bin:/usr/local/bin:/usr/bin:/bin", home))
137 .env("TMPDIR", &scratch)
138 // Inherited, so the probe measures the same build a caller would get.
139 // Named explicitly rather than passed through by `env_clear`'s absence,
140 // because a fenced cargo writing somewhere the fence does not grant is
141 // the second way this question is answered wrongly.
142 .env("CARGO_TARGET_DIR", std::env::var("CARGO_TARGET_DIR")
143 .unwrap_or_else(|_| format!("{}/.cache/cargo-targets/buildfence", home)))
144 .output();
145 match out {
146 Ok(o) => {
147 let err = String::from_utf8_lossy(&o.stderr);
148 println!("\n== {} -> exit {:?}", what, o.status.code());
149 for line in err.lines().take(8) {
150 println!(" {}", line);
151 }
152 },
153 Err(e) => println!("\n== {} -> could not spawn cargo: {}", what, e),
154 }
155 }
156}