Skip to content

Commit edd9d03

Browse files
committed
store: Introduce BootedOstree and BootedComposefs types
Refactor the storage layer to introduce new types that better encapsulate boot context: - Add BootedStorage wrapper that detects ostree vs composefs boot - Add BootedOstree struct holding sysroot reference and deployment - Add BootedComposefs struct holding repository and cmdline - Add BootedStorageKind enum to discriminate between backends This allows passing boot context as a cohesive unit rather than separate parameters. Update the upgrade code path to use these new types: - Change get_status() to accept &BootedOstree instead of separate params - Update handle_staged_soft_reboot() similarly - Update upgrade() to use structs throughout Add comprehensive documentation to all new types and methods in store/mod.rs. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters <[email protected]>
1 parent a043c8e commit edd9d03

File tree

7 files changed

+265
-106
lines changed

7 files changed

+265
-106
lines changed

crates/lib/src/bootc_composefs/status.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,23 @@ async fn boot_entry_from_composefs_deployment(
247247
return Ok(e);
248248
}
249249

250-
#[context("Getting composefs deployment status")]
251250
pub(crate) async fn composefs_deployment_status() -> Result<Host> {
252251
let composefs_state = composefs_booted()?
253252
.ok_or_else(|| anyhow::anyhow!("Failed to find composefs parameter in kernel cmdline"))?;
254-
let composefs_digest = &composefs_state.digest;
255253

256254
let sysroot =
257255
Dir::open_ambient_dir("/sysroot", ambient_authority()).context("Opening sysroot")?;
256+
257+
composefs_deployment_status_from(&sysroot, composefs_state).await
258+
}
259+
260+
#[context("Getting composefs deployment status")]
261+
pub(crate) async fn composefs_deployment_status_from(
262+
sysroot: &Dir,
263+
cmdline: &ComposefsCmdline,
264+
) -> Result<Host> {
265+
let composefs_digest = &cmdline.digest;
266+
258267
let deployments = sysroot
259268
.read_dir(STATE_DIR_RELATIVE)
260269
.with_context(|| format!("Reading sysroot {STATE_DIR_RELATIVE}"))?;

crates/lib/src/bootc_composefs/update.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
},
1515
cli::UpgradeOpts,
1616
spec::ImageReference,
17-
store::ComposefsRepository,
17+
store::{BootedComposefs, ComposefsRepository, Storage},
1818
};
1919

2020
use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
@@ -61,7 +61,11 @@ async fn is_image_pulled(
6161
}
6262

6363
#[context("Upgrading composefs")]
64-
pub(crate) async fn upgrade_composefs(opts: UpgradeOpts) -> Result<()> {
64+
pub(crate) async fn upgrade_composefs(
65+
opts: UpgradeOpts,
66+
_storage: &Storage,
67+
_composefs: &BootedComposefs,
68+
) -> Result<()> {
6569
let host = composefs_deployment_status()
6670
.await
6771
.context("Getting composefs deployment status")?;

crates/lib/src/cli.rs

Lines changed: 68 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ use crate::bootc_composefs::{
3838
finalize::{composefs_backend_finalize, get_etc_diff},
3939
rollback::composefs_rollback,
4040
state::composefs_usr_overlay,
41-
status::composefs_booted,
4241
switch::switch_composefs,
4342
update::upgrade_composefs,
4443
};
@@ -48,7 +47,8 @@ use crate::podstorage::set_additional_image_store;
4847
use crate::progress_jsonl::{ProgressWriter, RawProgressFd};
4948
use crate::spec::Host;
5049
use crate::spec::ImageReference;
51-
use crate::store::ComposefsRepository;
50+
use crate::store::{BootedOstree, ComposefsRepository, Storage};
51+
use crate::store::{BootedStorage, BootedStorageKind};
5252
use crate::utils::sigpolicy_from_opt;
5353

5454
/// Shared progress options
@@ -712,24 +712,11 @@ pub(crate) fn ensure_self_unshared_mount_namespace() -> Result<()> {
712712
bootc_utils::reexec::reexec_with_guardenv(recurse_env, &["unshare", "-m", "--"])
713713
}
714714

715-
/// Acquire a locked sysroot.
716-
/// TODO drain this and the above into SysrootLock
717-
#[context("Acquiring sysroot")]
718-
pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootLock> {
719-
prepare_for_write()?;
720-
let sysroot = ostree::Sysroot::new_default();
721-
sysroot.set_mount_namespace_in_use();
722-
let sysroot = ostree_ext::sysroot::SysrootLock::new_from_sysroot(&sysroot).await?;
723-
sysroot.load(gio::Cancellable::NONE)?;
724-
Ok(sysroot)
725-
}
726-
727715
/// Load global storage state, expecting that we're booted into a bootc system.
728716
#[context("Initializing storage")]
729-
pub(crate) async fn get_storage() -> Result<crate::store::Storage> {
730-
let global_run = Dir::open_ambient_dir("/run", cap_std::ambient_authority())?;
731-
let sysroot = get_locked_sysroot().await?;
732-
crate::store::Storage::new(sysroot, &global_run)
717+
pub(crate) async fn get_storage() -> Result<crate::store::BootedStorage> {
718+
prepare_for_write()?;
719+
BootedStorage::new().await
733720
}
734721

735722
#[context("Querying root privilege")]
@@ -811,15 +798,15 @@ where
811798
/// Handle soft reboot for staged deployments (used by upgrade and switch)
812799
#[context("Handling staged soft reboot")]
813800
fn handle_staged_soft_reboot(
814-
sysroot: &SysrootLock,
801+
booted_ostree: &BootedOstree<'_>,
815802
soft_reboot_mode: Option<SoftRebootMode>,
816803
host: &crate::spec::Host,
817804
) -> Result<()> {
818805
handle_soft_reboot(
819806
soft_reboot_mode,
820807
host.status.staged.as_ref(),
821808
"staged",
822-
|| soft_reboot_staged(sysroot),
809+
|| soft_reboot_staged(booted_ostree.sysroot),
823810
)
824811
}
825812

@@ -890,11 +877,14 @@ fn prepare_for_write() -> Result<()> {
890877

891878
/// Implementation of the `bootc upgrade` CLI command.
892879
#[context("Upgrading")]
893-
async fn upgrade(opts: UpgradeOpts) -> Result<()> {
894-
let sysroot = &get_storage().await?;
895-
let ostree = sysroot.get_ostree()?;
896-
let repo = &ostree.repo();
897-
let (booted_deployment, _deployments, host) = crate::status::get_status_require_booted(ostree)?;
880+
async fn upgrade(
881+
opts: UpgradeOpts,
882+
storage: &Storage,
883+
booted_ostree: &BootedOstree<'_>,
884+
) -> Result<()> {
885+
let repo = &booted_ostree.repo();
886+
887+
let host = crate::status::get_status(booted_ostree)?.1;
898888
let imgref = host.spec.image.as_ref();
899889
let prog: ProgressWriter = opts.progress.try_into()?;
900890

@@ -962,16 +952,16 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
962952
.unwrap_or_default();
963953
if staged_unchanged {
964954
println!("Staged update present, not changed.");
965-
handle_staged_soft_reboot(ostree, opts.soft_reboot, &host)?;
955+
handle_staged_soft_reboot(booted_ostree, opts.soft_reboot, &host)?;
966956
if opts.apply {
967957
crate::reboot::reboot()?;
968958
}
969959
} else if booted_unchanged {
970960
println!("No update available.")
971961
} else {
972-
let stateroot = booted_deployment.osname();
973-
let from = MergeState::from_stateroot(sysroot, &stateroot)?;
974-
crate::deploy::stage(sysroot, from, &fetched, &spec, prog.clone()).await?;
962+
let stateroot = booted_ostree.stateroot();
963+
let from = MergeState::from_stateroot(storage, &stateroot)?;
964+
crate::deploy::stage(storage, from, &fetched, &spec, prog.clone()).await?;
975965
changed = true;
976966
if let Some(prev) = booted_image.as_ref() {
977967
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
@@ -983,13 +973,13 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
983973
}
984974
}
985975
if changed {
986-
sysroot.update_mtime()?;
976+
storage.update_mtime()?;
987977

988978
if opts.soft_reboot.is_some() {
989979
// At this point we have new staged deployment and the host definition has changed.
990980
// We need the updated host status before we check if we can prepare the soft-reboot.
991-
let updated_host = crate::status::get_status(ostree, Some(&booted_deployment))?.1;
992-
handle_staged_soft_reboot(ostree, opts.soft_reboot, &updated_host)?;
981+
let updated_host = crate::status::get_status(booted_ostree)?.1;
982+
handle_staged_soft_reboot(booted_ostree, opts.soft_reboot, &updated_host)?;
993983
}
994984

995985
if opts.apply {
@@ -1039,8 +1029,8 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10391029

10401030
let cancellable = gio::Cancellable::NONE;
10411031

1042-
let sysroot = &get_storage().await?;
1043-
let ostree = sysroot.get_ostree()?;
1032+
let storage = &get_storage().await?;
1033+
let ostree = storage.get_ostree()?;
10441034
let repo = &ostree.repo();
10451035
let (booted_deployment, _deployments, host) = crate::status::get_status_require_booted(ostree)?;
10461036

@@ -1090,16 +1080,20 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10901080
}
10911081

10921082
let stateroot = booted_deployment.osname();
1093-
let from = MergeState::from_stateroot(sysroot, &stateroot)?;
1094-
crate::deploy::stage(sysroot, from, &fetched, &new_spec, prog.clone()).await?;
1083+
let from = MergeState::from_stateroot(storage, &stateroot)?;
1084+
crate::deploy::stage(storage, from, &fetched, &new_spec, prog.clone()).await?;
10951085

1096-
sysroot.update_mtime()?;
1086+
storage.update_mtime()?;
10971087

10981088
if opts.soft_reboot.is_some() {
10991089
// At this point we have staged the deployment and the host definition has changed.
11001090
// We need the updated host status before we check if we can prepare the soft-reboot.
1101-
let updated_host = crate::status::get_status(ostree, Some(&booted_deployment))?.1;
1102-
handle_staged_soft_reboot(ostree, opts.soft_reboot, &updated_host)?;
1091+
let booted_ostree = BootedOstree {
1092+
sysroot: ostree,
1093+
deployment: booted_deployment.clone(),
1094+
};
1095+
let updated_host = crate::status::get_status(&booted_ostree)?.1;
1096+
handle_staged_soft_reboot(&booted_ostree, opts.soft_reboot, &updated_host)?;
11031097
}
11041098

11051099
if opts.apply {
@@ -1112,9 +1106,9 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
11121106
/// Implementation of the `bootc rollback` CLI command.
11131107
#[context("Rollback")]
11141108
async fn rollback(opts: &RollbackOpts) -> Result<()> {
1115-
let sysroot = &get_storage().await?;
1116-
let ostree = sysroot.get_ostree()?;
1117-
crate::deploy::rollback(sysroot).await?;
1109+
let storage = &get_storage().await?;
1110+
let ostree = storage.get_ostree()?;
1111+
crate::deploy::rollback(storage).await?;
11181112

11191113
if opts.soft_reboot.is_some() {
11201114
// Get status of rollback deployment to check soft-reboot capability
@@ -1134,8 +1128,8 @@ async fn rollback(opts: &RollbackOpts) -> Result<()> {
11341128
/// Implementation of the `bootc edit` CLI command.
11351129
#[context("Editing spec")]
11361130
async fn edit(opts: EditOpts) -> Result<()> {
1137-
let sysroot = &get_storage().await?;
1138-
let ostree = sysroot.get_ostree()?;
1131+
let storage = &get_storage().await?;
1132+
let ostree = storage.get_ostree()?;
11391133
let repo = &ostree.repo();
11401134

11411135
let (booted_deployment, _deployments, host) = crate::status::get_status_require_booted(ostree)?;
@@ -1162,18 +1156,18 @@ async fn edit(opts: EditOpts) -> Result<()> {
11621156
// We only support two state transitions right now; switching the image,
11631157
// or flipping the bootloader ordering.
11641158
if host.spec.boot_order != new_host.spec.boot_order {
1165-
return crate::deploy::rollback(sysroot).await;
1159+
return crate::deploy::rollback(storage).await;
11661160
}
11671161

11681162
let fetched = crate::deploy::pull(repo, new_spec.image, None, opts.quiet, prog.clone()).await?;
11691163

11701164
// TODO gc old layers here
11711165

11721166
let stateroot = booted_deployment.osname();
1173-
let from = MergeState::from_stateroot(sysroot, &stateroot)?;
1174-
crate::deploy::stage(sysroot, from, &fetched, &new_spec, prog.clone()).await?;
1167+
let from = MergeState::from_stateroot(storage, &stateroot)?;
1168+
crate::deploy::stage(storage, from, &fetched, &new_spec, prog.clone()).await?;
11751169

1176-
sysroot.update_mtime()?;
1170+
storage.update_mtime()?;
11771171

11781172
Ok(())
11791173
}
@@ -1273,24 +1267,28 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
12731267
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
12741268
match opt {
12751269
Opt::Upgrade(opts) => {
1276-
if composefs_booted()?.is_some() {
1277-
upgrade_composefs(opts).await
1278-
} else {
1279-
upgrade(opts).await
1270+
let storage = &get_storage().await?;
1271+
match storage.kind()? {
1272+
BootedStorageKind::Ostree(booted_ostree) => {
1273+
upgrade(opts, storage, &booted_ostree).await
1274+
}
1275+
BootedStorageKind::Composefs(booted_cfs) => {
1276+
upgrade_composefs(opts, storage, &booted_cfs).await
1277+
}
12801278
}
12811279
}
12821280
Opt::Switch(opts) => {
1283-
if composefs_booted()?.is_some() {
1284-
switch_composefs(opts).await
1285-
} else {
1286-
switch(opts).await
1281+
let storage = &get_storage().await?;
1282+
match storage.kind()? {
1283+
BootedStorageKind::Ostree(_) => switch(opts).await,
1284+
BootedStorageKind::Composefs(_) => switch_composefs(opts).await,
12871285
}
12881286
}
12891287
Opt::Rollback(opts) => {
1290-
if composefs_booted()?.is_some() {
1291-
composefs_rollback().await?
1292-
} else {
1293-
rollback(&opts).await?
1288+
let storage = &get_storage().await?;
1289+
match storage.kind()? {
1290+
BootedStorageKind::Ostree(_) => rollback(&opts).await?,
1291+
BootedStorageKind::Composefs(_) => composefs_rollback().await?,
12941292
}
12951293

12961294
if opts.apply {
@@ -1301,10 +1299,10 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
13011299
}
13021300
Opt::Edit(opts) => edit(opts).await,
13031301
Opt::UsrOverlay => {
1304-
if composefs_booted()?.is_some() {
1305-
composefs_usr_overlay()
1306-
} else {
1307-
usroverlay().await
1302+
let storage = &get_storage().await?;
1303+
match storage.kind()? {
1304+
BootedStorageKind::Ostree(_) => usroverlay().await,
1305+
BootedStorageKind::Composefs(_) => composefs_usr_overlay(),
13081306
}
13091307
}
13101308
Opt::Container(opts) => match opts {
@@ -1400,8 +1398,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
14001398
crate::image::push_entrypoint(source.as_deref(), target.as_deref()).await
14011399
}
14021400
ImageOpts::PullFromDefaultStorage { image } => {
1403-
let sysroot = get_storage().await?;
1404-
sysroot
1401+
let storage = get_storage().await?;
1402+
storage
14051403
.get_ensure_imgstore()?
14061404
.pull_from_host_storage(&image)
14071405
.await
@@ -1501,8 +1499,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
15011499
InternalsOpts::Cfs { args } => crate::cfsctl::run_from_iter(args.iter()).await,
15021500
InternalsOpts::Reboot => crate::reboot::reboot(),
15031501
InternalsOpts::Fsck => {
1504-
let sysroot = &get_storage().await?;
1505-
crate::fsck::fsck(&sysroot, std::io::stdout().lock()).await?;
1502+
let storage = &get_storage().await?;
1503+
crate::fsck::fsck(&storage, std::io::stdout().lock()).await?;
15061504
Ok(())
15071505
}
15081506
InternalsOpts::FixupEtcFstab => crate::deploy::fixup_etc_fstab(&root),
@@ -1516,8 +1514,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
15161514
Ok(())
15171515
}
15181516
InternalsOpts::Cleanup => {
1519-
let sysroot = get_storage().await?;
1520-
crate::deploy::cleanup(&sysroot).await
1517+
let storage = get_storage().await?;
1518+
crate::deploy::cleanup(&storage).await
15211519
}
15221520
InternalsOpts::Relabel { as_path, path } => {
15231521
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;

crates/lib/src/image.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ async fn list_images(list_type: ImageListType) -> Result<Vec<ImageOutput>> {
7272
let rootfs = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())
7373
.context("Opening /")?;
7474

75-
let sysroot: Option<crate::store::Storage> =
75+
let sysroot: Option<crate::store::BootedStorage> =
7676
if ostree_ext::container_utils::running_in_container() {
7777
None
7878
} else {

crates/lib/src/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
811811

812812
sysroot.load(cancellable)?;
813813
let sysroot = SysrootLock::new_from_sysroot(&sysroot).await?;
814-
let storage = Storage::new(sysroot, &temp_run)?;
814+
let storage = Storage::new_ostree(sysroot, &temp_run)?;
815815

816816
Ok((storage, has_ostree))
817817
}

0 commit comments

Comments
 (0)