Skip to content

Commit 92409e9

Browse files
authored
Merge pull request #1477 from cgwalters/store-composefs-2
Add bootc internals cfs
2 parents 1532e74 + 9d3ccd0 commit 92409e9

File tree

6 files changed

+380
-0
lines changed

6 files changed

+380
-0
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/lib/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ bootc-mount = { path = "../mount" }
2323
bootc-tmpfiles = { path = "../tmpfiles" }
2424
bootc-sysusers = { path = "../sysusers" }
2525
camino = { workspace = true, features = ["serde1"] }
26+
composefs = { workspace = true }
27+
composefs-boot = { workspace = true }
28+
composefs-oci = { workspace = true }
2629
ostree-ext = { path = "../ostree-ext", features = ["bootc"] }
2730
chrono = { workspace = true, features = ["serde"] }
2831
clap = { workspace = true, features = ["derive","cargo"] }

crates/lib/src/cfsctl.rs

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
use std::{
2+
ffi::OsString,
3+
fs::create_dir_all,
4+
path::{Path, PathBuf},
5+
sync::Arc,
6+
};
7+
8+
use anyhow::Result;
9+
use clap::{Parser, Subcommand};
10+
11+
use rustix::fs::CWD;
12+
13+
use composefs_boot::{write_boot, BootOps};
14+
15+
use composefs::{
16+
fsverity::{FsVerityHashValue, Sha512HashValue},
17+
repository::Repository,
18+
};
19+
20+
/// cfsctl
21+
#[derive(Debug, Parser)]
22+
#[clap(name = "cfsctl", version)]
23+
pub struct App {
24+
#[clap(long, group = "repopath")]
25+
repo: Option<PathBuf>,
26+
#[clap(long, group = "repopath")]
27+
user: bool,
28+
#[clap(long, group = "repopath")]
29+
system: bool,
30+
31+
/// Sets the repository to insecure before running any operation and
32+
/// prepend '?' to the composefs kernel command line when writing
33+
/// boot entry.
34+
#[clap(long)]
35+
insecure: bool,
36+
37+
#[clap(subcommand)]
38+
cmd: Command,
39+
}
40+
41+
#[derive(Debug, Subcommand)]
42+
enum OciCommand {
43+
/// Stores a tar file as a splitstream in the repository.
44+
ImportLayer {
45+
sha256: String,
46+
name: Option<String>,
47+
},
48+
/// Lists the contents of a tar stream
49+
LsLayer {
50+
/// the name of the stream
51+
name: String,
52+
},
53+
Dump {
54+
config_name: String,
55+
config_verity: Option<String>,
56+
},
57+
Pull {
58+
image: String,
59+
name: Option<String>,
60+
},
61+
ComputeId {
62+
config_name: String,
63+
config_verity: Option<String>,
64+
#[clap(long)]
65+
bootable: bool,
66+
},
67+
CreateImage {
68+
config_name: String,
69+
config_verity: Option<String>,
70+
#[clap(long)]
71+
bootable: bool,
72+
#[clap(long)]
73+
image_name: Option<String>,
74+
},
75+
Seal {
76+
config_name: String,
77+
config_verity: Option<String>,
78+
},
79+
Mount {
80+
name: String,
81+
mountpoint: String,
82+
},
83+
PrepareBoot {
84+
config_name: String,
85+
config_verity: Option<String>,
86+
#[clap(long, default_value = "/boot")]
87+
bootdir: PathBuf,
88+
#[clap(long)]
89+
entry_id: Option<String>,
90+
#[clap(long)]
91+
cmdline: Vec<String>,
92+
},
93+
}
94+
95+
#[derive(Debug, Subcommand)]
96+
enum Command {
97+
/// Take a transaction lock on the repository.
98+
/// This prevents garbage collection from occurring.
99+
Transaction,
100+
/// Reconstitutes a split stream and writes it to stdout
101+
Cat {
102+
/// the name of the stream to cat, either a sha256 digest or prefixed with 'ref/'
103+
name: String,
104+
},
105+
/// Perform garbage collection
106+
GC,
107+
/// Imports a composefs image (unsafe!)
108+
ImportImage {
109+
reference: String,
110+
},
111+
/// Commands for dealing with OCI layers
112+
Oci {
113+
#[clap(subcommand)]
114+
cmd: OciCommand,
115+
},
116+
/// Mounts a composefs, possibly enforcing fsverity of the image
117+
Mount {
118+
/// the name of the image to mount, either a sha256 digest or prefixed with 'ref/'
119+
name: String,
120+
/// the mountpoint
121+
mountpoint: String,
122+
},
123+
CreateImage {
124+
path: PathBuf,
125+
#[clap(long)]
126+
bootable: bool,
127+
#[clap(long)]
128+
stat_root: bool,
129+
image_name: Option<String>,
130+
},
131+
ComputeId {
132+
path: PathBuf,
133+
#[clap(long)]
134+
bootable: bool,
135+
#[clap(long)]
136+
stat_root: bool,
137+
},
138+
CreateDumpfile {
139+
path: PathBuf,
140+
#[clap(long)]
141+
bootable: bool,
142+
#[clap(long)]
143+
stat_root: bool,
144+
},
145+
ImageObjects {
146+
name: String,
147+
},
148+
}
149+
150+
fn verity_opt(opt: &Option<String>) -> Result<Option<Sha512HashValue>> {
151+
Ok(opt
152+
.as_ref()
153+
.map(|value| FsVerityHashValue::from_hex(value))
154+
.transpose()?)
155+
}
156+
157+
pub(crate) async fn run_from_iter<I>(system_store: &crate::store::Storage, args: I) -> Result<()>
158+
where
159+
I: IntoIterator,
160+
I::Item: Into<OsString> + Clone,
161+
{
162+
let args = App::parse_from(
163+
std::iter::once(OsString::from("cfs")).chain(args.into_iter().map(Into::into)),
164+
);
165+
166+
let repo = if let Some(path) = &args.repo {
167+
let mut r = Repository::open_path(CWD, path)?;
168+
r.set_insecure(args.insecure);
169+
Arc::new(r)
170+
} else if args.user {
171+
let mut r = Repository::open_user()?;
172+
r.set_insecure(args.insecure);
173+
Arc::new(r)
174+
} else {
175+
if args.insecure {
176+
anyhow::bail!("Cannot override insecure state for system repo");
177+
}
178+
system_store.get_ensure_composefs()?
179+
};
180+
let repo = &repo;
181+
182+
match args.cmd {
183+
Command::Transaction => {
184+
// just wait for ^C
185+
loop {
186+
std::thread::park();
187+
}
188+
}
189+
Command::Cat { name } => {
190+
repo.merge_splitstream(&name, None, &mut std::io::stdout())?;
191+
}
192+
Command::ImportImage { reference } => {
193+
let image_id = repo.import_image(&reference, &mut std::io::stdin())?;
194+
println!("{}", image_id.to_id());
195+
}
196+
Command::Oci { cmd: oci_cmd } => match oci_cmd {
197+
OciCommand::ImportLayer { name, sha256 } => {
198+
let object_id = composefs_oci::import_layer(
199+
&repo,
200+
&composefs::util::parse_sha256(sha256)?,
201+
name.as_deref(),
202+
&mut std::io::stdin(),
203+
)?;
204+
println!("{}", object_id.to_id());
205+
}
206+
OciCommand::LsLayer { name } => {
207+
composefs_oci::ls_layer(&repo, &name)?;
208+
}
209+
OciCommand::Dump {
210+
ref config_name,
211+
ref config_verity,
212+
} => {
213+
let verity = verity_opt(config_verity)?;
214+
let mut fs =
215+
composefs_oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
216+
fs.print_dumpfile()?;
217+
}
218+
OciCommand::ComputeId {
219+
ref config_name,
220+
ref config_verity,
221+
bootable,
222+
} => {
223+
let verity = verity_opt(config_verity)?;
224+
let mut fs =
225+
composefs_oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
226+
if bootable {
227+
fs.transform_for_boot(&repo)?;
228+
}
229+
let id = fs.compute_image_id();
230+
println!("{}", id.to_hex());
231+
}
232+
OciCommand::CreateImage {
233+
ref config_name,
234+
ref config_verity,
235+
bootable,
236+
ref image_name,
237+
} => {
238+
let verity = verity_opt(config_verity)?;
239+
let mut fs =
240+
composefs_oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
241+
if bootable {
242+
fs.transform_for_boot(&repo)?;
243+
}
244+
let image_id = fs.commit_image(&repo, image_name.as_deref())?;
245+
println!("{}", image_id.to_id());
246+
}
247+
OciCommand::Pull { ref image, name } => {
248+
let (sha256, verity) = composefs_oci::pull(&repo, image, name.as_deref()).await?;
249+
250+
println!("sha256 {}", hex::encode(sha256));
251+
println!("verity {}", verity.to_hex());
252+
}
253+
OciCommand::Seal {
254+
ref config_name,
255+
ref config_verity,
256+
} => {
257+
let verity = verity_opt(config_verity)?;
258+
let (sha256, verity) = composefs_oci::seal(&repo, config_name, verity.as_ref())?;
259+
println!("sha256 {}", hex::encode(sha256));
260+
println!("verity {}", verity.to_id());
261+
}
262+
OciCommand::Mount {
263+
ref name,
264+
ref mountpoint,
265+
} => {
266+
composefs_oci::mount(&repo, name, mountpoint, None)?;
267+
}
268+
OciCommand::PrepareBoot {
269+
ref config_name,
270+
ref config_verity,
271+
ref bootdir,
272+
ref entry_id,
273+
ref cmdline,
274+
} => {
275+
let verity = verity_opt(config_verity)?;
276+
let mut fs =
277+
composefs_oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
278+
let entries = fs.transform_for_boot(&repo)?;
279+
let id = fs.commit_image(&repo, None)?;
280+
281+
let Some(entry) = entries.into_iter().next() else {
282+
anyhow::bail!("No boot entries!");
283+
};
284+
285+
let cmdline_refs: Vec<&str> = cmdline.iter().map(String::as_str).collect();
286+
write_boot::write_boot_simple(
287+
&repo,
288+
entry,
289+
&id,
290+
args.insecure,
291+
bootdir,
292+
None,
293+
entry_id.as_deref(),
294+
&cmdline_refs,
295+
)?;
296+
297+
let state = args
298+
.repo
299+
.as_ref()
300+
.map(|p: &PathBuf| p.parent().unwrap_or(p))
301+
.unwrap_or(Path::new("/sysroot"))
302+
.join("state/deploy")
303+
.join(id.to_hex());
304+
305+
create_dir_all(state.join("var"))?;
306+
create_dir_all(state.join("etc/upper"))?;
307+
create_dir_all(state.join("etc/work"))?;
308+
}
309+
},
310+
Command::ComputeId {
311+
ref path,
312+
bootable,
313+
stat_root,
314+
} => {
315+
let mut fs = composefs::fs::read_filesystem(CWD, path, Some(&repo), stat_root)?;
316+
if bootable {
317+
fs.transform_for_boot(&repo)?;
318+
}
319+
let id = fs.compute_image_id();
320+
println!("{}", id.to_hex());
321+
}
322+
Command::CreateImage {
323+
ref path,
324+
bootable,
325+
stat_root,
326+
ref image_name,
327+
} => {
328+
let mut fs = composefs::fs::read_filesystem(CWD, path, Some(&repo), stat_root)?;
329+
if bootable {
330+
fs.transform_for_boot(&repo)?;
331+
}
332+
let id = fs.commit_image(&repo, image_name.as_deref())?;
333+
println!("{}", id.to_id());
334+
}
335+
Command::CreateDumpfile {
336+
ref path,
337+
bootable,
338+
stat_root,
339+
} => {
340+
let mut fs = composefs::fs::read_filesystem(CWD, path, Some(&repo), stat_root)?;
341+
if bootable {
342+
fs.transform_for_boot(&repo)?;
343+
}
344+
fs.print_dumpfile()?;
345+
}
346+
Command::Mount { name, mountpoint } => {
347+
repo.mount_at(&name, &mountpoint)?;
348+
}
349+
Command::ImageObjects { name } => {
350+
let objects = repo.objects_for_image(&name)?;
351+
for object in objects {
352+
println!("{}", object.to_id());
353+
}
354+
}
355+
Command::GC => {
356+
repo.gc()?;
357+
}
358+
}
359+
Ok(())
360+
}

0 commit comments

Comments
 (0)