Skip to content

Commit e8da148

Browse files
Handle generate metadata and Bootloader Updates
For `bootupd generate-update-metadata` we now generate metadata for all found bootloaders. The final json will look like ```json { "timestamp": "2026-06-10T09:52:58.107743026Z", "version": "grub-cc-1:2.12-59.fc45,grub2-1:2.12-58.fc44,shim-16.1-5", "versions": [ { "name": "grub-cc", "rpm_evr": "1:2.12-59.fc45" }, { "name": "grub2", "rpm_evr": "1:2.12-58.fc44" }, { "name": "shim", "rpm_evr": "16.1-5" } ] } ``` This allows us to have multiple bootloaders in a single image and install/update based on preference. During install/update, we check to bootloader and filter out any and all entries from the update metadata that do not match or are not required for the bootloader Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent c8bd774 commit e8da148

6 files changed

Lines changed: 113 additions & 83 deletions

File tree

src/bios.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ impl Component for Bios {
116116
bootloader: Bootloader,
117117
) -> Result<InstalledContent> {
118118
if !self.is_bootloader_supported(bootloader) {
119-
anyhow::bail!("{bootloader} cannot be installed for bios");
119+
anyhow::bail!("{bootloader} cannot be installed for {}", self.name());
120120
}
121121

122122
let device =
123123
device.ok_or_else(|| anyhow::anyhow!("BIOS component requires a target device"))?;
124124
let src_dir = Dir::open_ambient_dir(src_root, ambient_authority())
125125
.with_context(|| format!("opening source directory {src_root}"))?;
126-
let Some(meta) = get_component_update(&src_dir, self)? else {
126+
let Some(meta) = get_component_update(&src_dir, self, Some(bootloader))? else {
127127
anyhow::bail!("No update metadata for component {} found", self.name());
128128
};
129129

@@ -137,15 +137,7 @@ impl Component for Bios {
137137
})
138138
}
139139

140-
fn generate_update_metadata(
141-
&self,
142-
sysroot_path: &str,
143-
bootloader: Bootloader,
144-
) -> Result<Option<ContentMetadata>> {
145-
if !self.is_bootloader_supported(bootloader) {
146-
anyhow::bail!("{bootloader} cannot be installed for bios");
147-
}
148-
140+
fn generate_update_metadata(&self, sysroot_path: &str) -> Result<Option<ContentMetadata>> {
149141
let grub_install = Path::new(sysroot_path).join(GRUB_BIN);
150142
if !grub_install.exists() {
151143
println!("Failed to find {:?}", grub_install);
@@ -263,8 +255,16 @@ impl Component for Bios {
263255
}))
264256
}
265257

266-
fn query_update(&self, sysroot: &Dir) -> Result<Option<ContentMetadata>> {
267-
get_component_update(sysroot, self)
258+
fn query_update(
259+
&self,
260+
sysroot: &Dir,
261+
bootloader: Bootloader,
262+
) -> Result<Option<ContentMetadata>> {
263+
if !self.is_bootloader_supported(bootloader) {
264+
return Ok(None);
265+
}
266+
267+
get_component_update(sysroot, self, Some(bootloader))
268268
}
269269

270270
fn query_requires_update(&self, sysroot: &Dir) -> Result<()> {
@@ -277,7 +277,7 @@ impl Component for Bios {
277277

278278
fn run_update(&self, rootcxt: &RootContext, _: &InstalledContent) -> Result<InstalledContent> {
279279
let updatemeta = self
280-
.query_update(&rootcxt.sysroot)?
280+
.query_update(&rootcxt.sysroot, Bootloader::Grub)?
281281
.expect("update available");
282282

283283
for parent in rootcxt.device.find_all_roots()? {

src/bootupd.rs

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::freezethaw::fsfreeze_thaw_cycle;
1919
))]
2020
use crate::grubconfigs::{ensure_grub_permissions, GRUB2DIR};
2121
use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedState, Status};
22-
use crate::util::running_in_container;
2322
use crate::{ostreeutil, util};
2423
use anyhow::{anyhow, Context, Result};
2524
use camino::{Utf8Path, Utf8PathBuf};
@@ -54,6 +53,7 @@ impl ConfigMode {
5453
}
5554
}
5655

56+
#[allow(clippy::too_many_arguments)]
5757
pub(crate) fn install(
5858
source_root: &str,
5959
dest_root: &str,
@@ -105,18 +105,21 @@ pub(crate) fn install(
105105
continue;
106106
}
107107

108-
// skip components that don't have an update metadata
109-
if component.query_update(&source_root_dir)?.is_none() {
108+
if !component.is_bootloader_supported(bootloader) {
110109
println!(
111-
"Skip installing component {} without update metadata",
110+
"Skip installing component {} as it does not support bootloader {bootloader}",
112111
component.name()
113112
);
114113
continue;
115114
}
116115

117-
if !component.is_bootloader_supported(bootloader) {
116+
// skip components that don't have an update metadata
117+
if component
118+
.query_update(&source_root_dir, bootloader)?
119+
.is_none()
120+
{
118121
println!(
119-
"Skip installing component {} as it does not support bootloader {bootloader}",
122+
"Skip installing component {} without update metadata",
120123
component.name()
121124
);
122125
continue;
@@ -283,22 +286,15 @@ pub(crate) fn get_components() -> Components {
283286
get_components_impl(false)
284287
}
285288

286-
pub(crate) fn generate_update_metadata(sysroot_path: &str, bootloader: Bootloader) -> Result<()> {
289+
pub(crate) fn generate_update_metadata(sysroot_path: &str) -> Result<()> {
287290
// create bootupd update dir which will save component metadata files for both components
288291
let updates_dir = Path::new(sysroot_path).join(crate::model::BOOTUPD_UPDATES_DIR);
292+
289293
std::fs::create_dir_all(&updates_dir)
290294
.with_context(|| format!("Failed to create updates dir {:?}", &updates_dir))?;
291-
for component in get_components().values() {
292-
if !component.is_bootloader_supported(bootloader) {
293-
println!(
294-
"Bootloader {bootloader} not supported for {}. Skipping metadata generation",
295-
component.name(),
296-
);
297-
298-
continue;
299-
}
300295

301-
if let Some(v) = component.generate_update_metadata(sysroot_path, bootloader)? {
296+
for component in get_components().values() {
297+
if let Some(v) = component.generate_update_metadata(sysroot_path)? {
302298
println!(
303299
"Generated update layout for {}: {}",
304300
component.name(),
@@ -343,7 +339,7 @@ pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdat
343339
anyhow::bail!("Component {} is not installed", name);
344340
};
345341
let sysroot = &rootcxt.sysroot;
346-
let update = component.query_update(sysroot)?;
342+
let update = component.query_update(sysroot, bootloader)?;
347343
let update = match update.as_ref() {
348344
Some(p) => match inst.meta.can_upgrade_to(p) {
349345
std::cmp::Ordering::Less => p, // current < available -> upgrade
@@ -364,10 +360,12 @@ pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdat
364360
target_arch = "riscv64"
365361
))]
366362
{
367-
let grub2dir = &sysroot
368-
.open_dir(format!("boot/{GRUB2DIR}"))
369-
.context("Opening /boot/grub2")?;
370-
ensure_grub_permissions(grub2dir)?;
363+
if bootloader == Bootloader::Grub {
364+
let grub2dir = &sysroot
365+
.open_dir(format!("boot/{GRUB2DIR}"))
366+
.context("Opening /boot/grub2")?;
367+
ensure_grub_permissions(grub2dir)?;
368+
}
371369
}
372370

373371
let mut pending_container = state.pending.take().unwrap_or_default();
@@ -417,13 +415,15 @@ pub(crate) fn adopt_and_update(
417415
target_arch = "riscv64"
418416
))]
419417
{
420-
let grub2dir = &sysroot
421-
.open_dir(format!("boot/{GRUB2DIR}"))
422-
.context("Opening /boot/grub2")?;
423-
ensure_grub_permissions(grub2dir)?;
418+
if bootloader == Bootloader::Grub {
419+
let grub2dir = &sysroot
420+
.open_dir(format!("boot/{GRUB2DIR}"))
421+
.context("Opening /boot/grub2")?;
422+
ensure_grub_permissions(grub2dir)?;
423+
}
424424
}
425425

426-
let Some(update) = component.query_update(sysroot)? else {
426+
let Some(update) = component.query_update(sysroot, bootloader)? else {
427427
anyhow::bail!("Component {} has no available update", name);
428428
};
429429

@@ -486,7 +486,7 @@ pub(crate) fn validate(name: &str) -> Result<ValidationResult> {
486486
pub(crate) fn status() -> Result<Status> {
487487
let mut ret: Status = Default::default();
488488
let mut known_components = get_components();
489-
let sysroot = Dir::open_ambient_dir("/", ambient_authority())?;
489+
let root = Dir::open_ambient_dir("/", ambient_authority())?;
490490

491491
let bootloader = get_bootloader()?;
492492
let state = SavedState::load_from_disk("/", Some(bootloader))?;
@@ -499,7 +499,7 @@ pub(crate) fn status() -> Result<Status> {
499499
.ok_or_else(|| anyhow!("Unknown component installed: {}", name))?;
500500
let component = component.as_ref();
501501
let interrupted = state.pending.as_ref().and_then(|p| p.get(name.as_str()));
502-
let update = component.query_update(&sysroot)?;
502+
let update = component.query_update(&root, bootloader)?;
503503
let updatable = ComponentUpdatable::from_metadata(&ic.meta, update.as_ref());
504504
let adopted_from = ic.adopted_from.clone();
505505
ret.components.insert(
@@ -538,7 +538,7 @@ pub(crate) fn status() -> Result<Status> {
538538
if let Some(adopt_ver) = component::query_adopt_state()? {
539539
let component = component::new_from_name(&name)?;
540540
// Skip if the update metadata could not be found
541-
if component.query_update(&sysroot)?.is_none() {
541+
if component.query_update(&root, bootloader)?.is_none() {
542542
continue;
543543
};
544544
ret.adoptable.insert(name.to_string(), adopt_ver);

src/cli/bootupd.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,6 @@ pub struct GenerateOpts {
9696
/// Physical root mountpoint
9797
#[clap(value_parser)]
9898
sysroot: Option<String>,
99-
100-
/// The bootloader to generate metadata for
101-
//
102-
// We have a default to not break older systems
103-
#[clap(long, default_value_t = Bootloader::Grub)]
104-
bootloader: Bootloader,
10599
}
106100

107101
impl DCommand {
@@ -119,8 +113,7 @@ impl DCommand {
119113
if sysroot != "/" {
120114
anyhow::bail!("Using a non-default sysroot is not supported: {}", sysroot);
121115
}
122-
bootupd::generate_update_metadata(sysroot, opts.bootloader)
123-
.context("generating metadata failed")?;
116+
bootupd::generate_update_metadata(sysroot).context("generating metadata failed")?;
124117
Ok(())
125118
}
126119

src/component.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ pub(crate) trait Component {
7171
/// this is an `rpm-ostree compose tree` for example. For a dual-partition
7272
/// style updater, this would be run as part of a postprocessing step
7373
/// while the filesystem for the partition is mounted.
74-
fn generate_update_metadata(
74+
fn generate_update_metadata(&self, sysroot: &str) -> Result<Option<ContentMetadata>>;
75+
76+
/// Used on the client to query for an update cached in the current booted OS.
77+
fn query_update(
7578
&self,
76-
sysroot: &str,
79+
sysroot: &Dir,
7780
bootloader: Bootloader,
7881
) -> Result<Option<ContentMetadata>>;
7982

80-
/// Used on the client to query for an update cached in the current booted OS.
81-
fn query_update(&self, sysroot: &Dir) -> Result<Option<ContentMetadata>>;
82-
8383
/// This is called in the update code if query_update() returned no metadata.
8484
/// It should return an error if the current booted system should expect some
8585
/// metadata for this component.
@@ -167,21 +167,37 @@ pub(crate) fn write_update_metadata(
167167
}
168168

169169
/// Given a component, return metadata on the available update (if any)
170+
//
171+
/// If bootloader is Some, all metadata not pertaining to the specified bootloader
172+
/// is filtered
173+
///
174+
/// If bootloader is None, no filtering is performed
170175
#[context("Loading update for component {}", component.name())]
171176
pub(crate) fn get_component_update(
172177
sysroot: &Dir,
173178
component: &dyn Component,
179+
bootloader: Option<Bootloader>,
174180
) -> Result<Option<ContentMetadata>> {
175181
let name = component_update_data_name(component);
176-
let path = Path::new(BOOTUPD_UPDATES_DIR).join(name);
177-
if let Some(f) = sysroot.open_optional(&path)? {
178-
let mut f = std::io::BufReader::new(f);
179-
let u = serde_json::from_reader(&mut f)
180-
.with_context(|| format!("failed to parse {:?}", &path))?;
181-
Ok(Some(u))
182-
} else {
183-
Ok(None)
184-
}
182+
let path = Path::new(BOOTUPD_UPDATES_DIR).join(&name);
183+
184+
let Some(f) = sysroot.open_optional(&path)? else {
185+
return Ok(None);
186+
};
187+
188+
let mut f = std::io::BufReader::new(f);
189+
let mut u =
190+
serde_json::from_reader(&mut f).with_context(|| format!("failed to parse {:?}", &path))?;
191+
192+
let Some(bootloader) = bootloader else {
193+
return Ok(Some(u));
194+
};
195+
196+
// We store metadata of all bootloaders present in the image
197+
// So here, we will now filter out the bootloaders
198+
u.filter_bootloader(bootloader);
199+
200+
Ok(Some(u))
185201
}
186202

187203
#[context("Querying adoptable state")]

src/efi.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use widestring::U16CString;
2424

2525
use bootc_internal_blockdev::Device;
2626

27-
use crate::bootloader::Bootloader;
27+
use crate::bootloader::{get_bootloader, Bootloader};
2828
use crate::bootupd::RootContext;
2929
use crate::freezethaw::fsfreeze_thaw_cycle;
3030
use crate::model::*;
@@ -449,7 +449,7 @@ impl Component for Efi {
449449
) -> Result<InstalledContent> {
450450
let src_dir = Dir::open_ambient_dir(src_root, ambient_authority())
451451
.with_context(|| format!("opening source directory {src_root}"))?;
452-
let Some(meta) = get_component_update(&src_dir, self)? else {
452+
let Some(meta) = get_component_update(&src_dir, self, Some(bootloader))? else {
453453
anyhow::bail!("No update metadata for component {} found", self.name());
454454
};
455455
log::debug!("Found metadata {}", meta.version);
@@ -533,7 +533,9 @@ impl Component for Efi {
533533
.as_ref()
534534
.ok_or_else(|| anyhow::anyhow!("No filetree for installed EFI found!"))?;
535535
let sysroot_dir = &rootcxt.sysroot;
536-
let updatemeta = self.query_update(sysroot_dir)?.expect("update available");
536+
let updatemeta = self
537+
.query_update(sysroot_dir, get_bootloader()?)?
538+
.expect("update available");
537539
let updated_path = {
538540
let efilib_path = rootcxt.path.join(EFILIB);
539541
if efilib_path.exists()
@@ -580,11 +582,7 @@ impl Component for Efi {
580582
})
581583
}
582584

583-
fn generate_update_metadata(
584-
&self,
585-
sysroot: &str,
586-
bootloader: Bootloader,
587-
) -> Result<Option<ContentMetadata>> {
585+
fn generate_update_metadata(&self, sysroot: &str) -> Result<Option<ContentMetadata>> {
588586
let sysroot_path = Utf8Path::new(sysroot);
589587
let sysroot_dir = Dir::open_ambient_dir(sysroot_path, cap_std::ambient_authority())?;
590588

@@ -596,7 +594,7 @@ impl Component for Efi {
596594
// have them in /usr/lib/ostree-boot, which we move to /usr/lib/bootupd/updates/EFI
597595
let metadata = if sysroot_path.join(EFILIB).exists() {
598596
println!("Generating metadata from {EFILIB}");
599-
generate_meta_from_usr_efi(sysroot_path, bootloader)?
597+
generate_meta_from_usr_efi(sysroot_path)?
600598
} else {
601599
match &ostreeboot {
602600
Some(..) => {
@@ -630,8 +628,12 @@ impl Component for Efi {
630628
Ok(Some(metadata))
631629
}
632630

633-
fn query_update(&self, sysroot: &Dir) -> Result<Option<ContentMetadata>> {
634-
get_component_update(sysroot, self)
631+
fn query_update(
632+
&self,
633+
sysroot: &Dir,
634+
bootloader: Bootloader,
635+
) -> Result<Option<ContentMetadata>> {
636+
get_component_update(sysroot, self, Some(bootloader))
635637
}
636638

637639
fn query_requires_update(&self, _sysroot: &Dir) -> Result<()> {
@@ -826,12 +828,11 @@ fn find_file_recursive<P: AsRef<Path>>(dir: P, target_file: &str) -> Result<Vec<
826828
}
827829

828830
#[context("Generating metadata from usr/lib/efi")]
829-
fn generate_meta_from_usr_efi(
830-
sysroot_path: &Utf8Path,
831-
bootloader: Bootloader,
832-
) -> Result<ContentMetadata> {
833-
let Some(efi_components) = get_efi_component_from_usr(sysroot_path, EFILIB, Some(bootloader))?
834-
else {
831+
fn generate_meta_from_usr_efi(sysroot_path: &Utf8Path) -> Result<ContentMetadata> {
832+
// We DO NOT want to filter while generating metadata
833+
// We want to have metadata for multiple bootloaders
834+
// Later on, while installing, we'll filter out the stuff we don't need
835+
let Some(efi_components) = get_efi_component_from_usr(sysroot_path, EFILIB, None)? else {
835836
anyhow::bail!("Failed to find EFI components");
836837
};
837838

0 commit comments

Comments
 (0)