Skip to content

Commit 33e4511

Browse files
Johan-Liebert1cgwalters
authored andcommitted
WIP: composefs backend
Signed-off-by: Pragyan Poudyal <[email protected]> Signed-off-by: Colin Walters <[email protected]>
1 parent 324c322 commit 33e4511

File tree

12 files changed

+1440
-54
lines changed

12 files changed

+1440
-54
lines changed

crates/lib/src/bls_config.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use anyhow::Result;
2+
use serde::de::Error;
3+
use serde::{Deserialize, Deserializer};
4+
use std::collections::HashMap;
5+
6+
#[derive(Debug, Deserialize, Eq)]
7+
pub(crate) struct BLSConfig {
8+
pub(crate) title: Option<String>,
9+
#[serde(deserialize_with = "deserialize_version")]
10+
pub(crate) version: u32,
11+
pub(crate) linux: String,
12+
pub(crate) initrd: String,
13+
pub(crate) options: String,
14+
15+
#[serde(flatten)]
16+
pub(crate) extra: HashMap<String, String>,
17+
}
18+
19+
impl PartialEq for BLSConfig {
20+
fn eq(&self, other: &Self) -> bool {
21+
self.version == other.version
22+
}
23+
}
24+
25+
impl PartialOrd for BLSConfig {
26+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
27+
self.version.partial_cmp(&other.version)
28+
}
29+
}
30+
31+
impl Ord for BLSConfig {
32+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
33+
self.version.cmp(&other.version)
34+
}
35+
}
36+
37+
impl BLSConfig {
38+
pub(crate) fn to_string(&self) -> String {
39+
let mut out = String::new();
40+
41+
if let Some(title) = &self.title {
42+
out += &format!("title {}\n", title);
43+
}
44+
45+
out += &format!("version {}\n", self.version);
46+
out += &format!("linux {}\n", self.linux);
47+
out += &format!("initrd {}\n", self.initrd);
48+
out += &format!("options {}\n", self.options);
49+
50+
for (key, value) in &self.extra {
51+
out += &format!("{} {}\n", key, value);
52+
}
53+
54+
out
55+
}
56+
}
57+
58+
fn deserialize_version<'de, D>(deserializer: D) -> Result<u32, D::Error>
59+
where
60+
D: Deserializer<'de>,
61+
{
62+
let s: Option<String> = Option::deserialize(deserializer)?;
63+
64+
match s {
65+
Some(s) => Ok(s.parse::<u32>().map_err(D::Error::custom)?),
66+
None => Err(D::Error::custom("Version not found")),
67+
}
68+
}
69+
70+
pub(crate) fn parse_bls_config(input: &str) -> Result<BLSConfig> {
71+
let mut map = HashMap::new();
72+
73+
for line in input.lines() {
74+
let line = line.trim();
75+
if line.is_empty() || line.starts_with('#') {
76+
continue;
77+
}
78+
79+
if let Some((key, value)) = line.split_once(' ') {
80+
map.insert(key.to_string(), value.trim().to_string());
81+
}
82+
}
83+
84+
let value = serde_json::to_value(map)?;
85+
let parsed: BLSConfig = serde_json::from_value(value)?;
86+
87+
Ok(parsed)
88+
}

crates/lib/src/bootloader.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@ pub(crate) fn install_via_bootupd(
1616
device: &PartitionTable,
1717
rootfs: &Utf8Path,
1818
configopts: &crate::install::InstallConfigOpts,
19-
deployment_path: &str,
19+
deployment_path: Option<&str>,
2020
) -> Result<()> {
2121
let verbose = std::env::var_os("BOOTC_BOOTLOADER_DEBUG").map(|_| "-vvvv");
2222
// bootc defaults to only targeting the platform boot method.
2323
let bootupd_opts = (!configopts.generic_image).then_some(["--update-firmware", "--auto"]);
2424

25-
let srcroot = rootfs.join(deployment_path);
25+
let abs_deployment_path = deployment_path.map(|v| rootfs.join(v));
26+
let src_root_arg = if let Some(p) = abs_deployment_path.as_deref() {
27+
vec!["--src-root", p.as_str()]
28+
} else {
29+
vec![]
30+
};
2631
let devpath = device.path();
2732
println!("Installing bootloader via bootupd");
2833
Command::new("bootupctl")
2934
.args(["backend", "install", "--write-uuid"])
3035
.args(verbose)
3136
.args(bootupd_opts.iter().copied().flatten())
32-
.args(["--src-root", srcroot.as_str()])
37+
.args(src_root_arg)
3338
.args(["--device", devpath.as_str(), rootfs.as_str()])
3439
.log_debug()
3540
.run_inherited_with_cmd_context()

crates/lib/src/cli.rs

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,22 @@ use ostree_ext::composefs::fsverity;
2121
use ostree_ext::composefs::fsverity::FsVerityHashValue;
2222
use ostree_ext::composefs::splitstream::SplitStreamWriter;
2323
use ostree_ext::container as ostree_container;
24-
use ostree_ext::container_utils::ostree_booted;
24+
use ostree_ext::container_utils::{composefs_booted, ostree_booted};
2525
use ostree_ext::keyfileext::KeyFileExt;
2626
use ostree_ext::ostree;
2727
use schemars::schema_for;
2828
use serde::{Deserialize, Serialize};
2929

30-
use crate::deploy::RequiredHostSpec;
30+
use crate::deploy::{composefs_rollback, RequiredHostSpec};
31+
use crate::install::{
32+
pull_composefs_repo, setup_composefs_bls_boot, setup_composefs_uki_boot, write_composefs_state,
33+
BootSetupType, BootType,
34+
};
3135
use crate::lints;
3236
use crate::progress_jsonl::{ProgressWriter, RawProgressFd};
3337
use crate::spec::Host;
3438
use crate::spec::ImageReference;
39+
use crate::status::composefs_deployment_status;
3540
use crate::utils::sigpolicy_from_opt;
3641

3742
/// Shared progress options
@@ -778,6 +783,53 @@ fn prepare_for_write() -> Result<()> {
778783
Ok(())
779784
}
780785

786+
#[context("Upgrading composefs")]
787+
async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
788+
// TODO: IMPORTANT Have all the checks here that `bootc upgrade` has for an ostree booted system
789+
790+
let host = composefs_deployment_status()
791+
.await
792+
.context("Getting composefs deployment status")?;
793+
794+
// TODO: IMPORTANT We need to check if any deployment is staged and get the image from that
795+
let imgref = host
796+
.spec
797+
.image
798+
.as_ref()
799+
.ok_or_else(|| anyhow::anyhow!("No image source specified"))?;
800+
801+
// let booted_image = host
802+
// .status
803+
// .booted
804+
// .ok_or(anyhow::anyhow!("Could not find booted image"))?
805+
// .image
806+
// .ok_or(anyhow::anyhow!("Could not find booted image"))?;
807+
808+
// tracing::debug!("booted_image: {booted_image:#?}");
809+
// tracing::debug!("imgref: {imgref:#?}");
810+
811+
// let digest = booted_image
812+
// .digest()
813+
// .context("Getting digest for booted image")?;
814+
815+
let (repo, entries, id) = pull_composefs_repo(&imgref.transport, &imgref.image).await?;
816+
817+
let Some(entry) = entries.into_iter().next() else {
818+
anyhow::bail!("No boot entries!");
819+
};
820+
821+
let boot_type = BootType::from(&entry);
822+
823+
match boot_type {
824+
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
825+
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
826+
}?;
827+
828+
write_composefs_state(&Utf8PathBuf::from("/sysroot"), id, imgref, true, boot_type)?;
829+
830+
Ok(())
831+
}
832+
781833
/// Implementation of the `bootc upgrade` CLI command.
782834
#[context("Upgrading")]
783835
async fn upgrade(opts: UpgradeOpts) -> Result<()> {
@@ -891,9 +943,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
891943
Ok(())
892944
}
893945

894-
/// Implementation of the `bootc switch` CLI command.
895-
#[context("Switching")]
896-
async fn switch(opts: SwitchOpts) -> Result<()> {
946+
fn imgref_for_switch(opts: &SwitchOpts) -> Result<ImageReference> {
897947
let transport = ostree_container::Transport::try_from(opts.transport.as_str())?;
898948
let imgref = ostree_container::ImageReference {
899949
transport,
@@ -902,6 +952,63 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
902952
let sigverify = sigpolicy_from_opt(opts.enforce_container_sigpolicy);
903953
let target = ostree_container::OstreeImageReference { sigverify, imgref };
904954
let target = ImageReference::from(target);
955+
956+
return Ok(target);
957+
}
958+
959+
#[context("Composefs Switching")]
960+
async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
961+
let target = imgref_for_switch(&opts)?;
962+
// TODO: Handle in-place
963+
964+
let host = composefs_deployment_status()
965+
.await
966+
.context("Getting composefs deployment status")?;
967+
968+
let new_spec = {
969+
let mut new_spec = host.spec.clone();
970+
new_spec.image = Some(target.clone());
971+
new_spec
972+
};
973+
974+
if new_spec == host.spec {
975+
println!("Image specification is unchanged.");
976+
return Ok(());
977+
}
978+
979+
let Some(target_imgref) = new_spec.image else {
980+
anyhow::bail!("Target image is undefined")
981+
};
982+
983+
let (repo, entries, id) = pull_composefs_repo(&"docker".into(), &target_imgref.image).await?;
984+
985+
let Some(entry) = entries.into_iter().next() else {
986+
anyhow::bail!("No boot entries!");
987+
};
988+
989+
let boot_type = BootType::from(&entry);
990+
991+
match boot_type {
992+
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
993+
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
994+
}?;
995+
996+
write_composefs_state(
997+
&Utf8PathBuf::from("/sysroot"),
998+
id,
999+
&target_imgref,
1000+
true,
1001+
boot_type,
1002+
)?;
1003+
1004+
Ok(())
1005+
}
1006+
1007+
/// Implementation of the `bootc switch` CLI command.
1008+
#[context("Switching")]
1009+
async fn switch(opts: SwitchOpts) -> Result<()> {
1010+
let target = imgref_for_switch(&opts)?;
1011+
9051012
let prog: ProgressWriter = opts.progress.try_into()?;
9061013

9071014
// If we're doing an in-place mutation, we shortcut most of the rest of the work here
@@ -966,8 +1073,12 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
9661073
/// Implementation of the `bootc rollback` CLI command.
9671074
#[context("Rollback")]
9681075
async fn rollback(opts: RollbackOpts) -> Result<()> {
969-
let sysroot = &get_storage().await?;
970-
crate::deploy::rollback(sysroot).await?;
1076+
if composefs_booted()? {
1077+
composefs_rollback().await?
1078+
} else {
1079+
let sysroot = &get_storage().await?;
1080+
crate::deploy::rollback(sysroot).await?;
1081+
};
9711082

9721083
if opts.apply {
9731084
crate::reboot::reboot()?;
@@ -1117,8 +1228,20 @@ impl Opt {
11171228
async fn run_from_opt(opt: Opt) -> Result<()> {
11181229
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
11191230
match opt {
1120-
Opt::Upgrade(opts) => upgrade(opts).await,
1121-
Opt::Switch(opts) => switch(opts).await,
1231+
Opt::Upgrade(opts) => {
1232+
if composefs_booted()? {
1233+
upgrade_composefs(opts).await
1234+
} else {
1235+
upgrade(opts).await
1236+
}
1237+
}
1238+
Opt::Switch(opts) => {
1239+
if composefs_booted()? {
1240+
switch_composefs(opts).await
1241+
} else {
1242+
switch(opts).await
1243+
}
1244+
}
11221245
Opt::Rollback(opts) => rollback(opts).await,
11231246
Opt::Edit(opts) => edit(opts).await,
11241247
Opt::UsrOverlay => usroverlay().await,
@@ -1259,8 +1382,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
12591382
FsverityOpts::Enable { path } => {
12601383
let fd =
12611384
std::fs::File::open(&path).with_context(|| format!("Reading {path}"))?;
1262-
// Note this is not robust to forks, we're not using the _maybe_copy variant
1263-
fsverity::enable_verity_with_retry::<fsverity::Sha256HashValue>(&fd)?;
1385+
fsverity::enable_verity_raw::<fsverity::Sha256HashValue>(&fd)?;
12641386
Ok(())
12651387
}
12661388
},

0 commit comments

Comments
 (0)