Skip to content

Commit e8f442c

Browse files
authored
Merge pull request #945 from HuijingHei/coreos-static-grub-migration
adopt: add tag to install the static GRUB config from tree
2 parents 57e0e29 + 52ff0f9 commit e8f442c

File tree

7 files changed

+173
-19
lines changed

7 files changed

+173
-19
lines changed

src/bios.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use anyhow::{bail, Result};
1+
use anyhow::{bail, Context, Result};
2+
use camino::Utf8PathBuf;
3+
use openat_ext::OpenatDirExt;
24
#[cfg(target_arch = "powerpc64")]
35
use std::borrow::Cow;
46
use std::io::prelude::*;
@@ -8,6 +10,7 @@ use std::process::Command;
810
use crate::blockdev;
911
use crate::bootupd::RootContext;
1012
use crate::component::*;
13+
use crate::grubconfigs;
1114
use crate::model::*;
1215
use crate::packagesystem;
1316

@@ -142,10 +145,70 @@ impl Component for Bios {
142145
crate::component::query_adopt_state()
143146
}
144147

148+
// Backup the current grub.cfg and replace with new static config
149+
// - Backup "/boot/loader/grub.cfg" to "/boot/grub2/grub.cfg.bak"
150+
// - Remove symlink "/boot/grub2/grub.cfg"
151+
// - Replace "/boot/grub2/grub.cfg" symlink with new static "grub.cfg"
152+
fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &openat::Dir) -> Result<()> {
153+
let grub = "boot/grub2";
154+
// sysroot_path is /, destdir is Dir of /
155+
let grub_config_path = Utf8PathBuf::from(sysroot_path).join(grub);
156+
let grub_config_dir = destdir.sub_dir(grub).context("Opening boot/grub2")?;
157+
158+
let grub_config = grub_config_path.join(grubconfigs::GRUBCONFIG);
159+
160+
if !grub_config.exists() {
161+
anyhow::bail!("Could not find '{}'", grub_config);
162+
}
163+
164+
let mut current_config;
165+
// If /boot/grub2/grub.cfg is not symlink, we need to keep going
166+
if !grub_config.is_symlink() {
167+
println!("'{}' is not a symlink", grub_config);
168+
current_config = grub_config.clone();
169+
} else {
170+
// If /boot/grub2/grub.cfg is symlink to /boot/loader/grub.cfg,
171+
// backup it to /boot/grub2/grub.cfg.bak
172+
// Get real file for symlink /boot/grub2/grub.cfg
173+
let real_config = grub_config_dir.read_link(grubconfigs::GRUBCONFIG)?;
174+
let real_config =
175+
Utf8PathBuf::from_path_buf(real_config).expect("Path should be valid UTF-8");
176+
// Resolve symlink location
177+
current_config = grub_config_path.clone();
178+
current_config.push(real_config);
179+
}
180+
181+
let backup_config = grub_config_path.join(grubconfigs::GRUBCONFIG_BACKUP);
182+
if !backup_config.exists() {
183+
// Backup the current GRUB config which is hopefully working right now
184+
println!(
185+
"Creating a backup of the current GRUB config '{}' in '{}'...",
186+
current_config, backup_config
187+
);
188+
std::fs::copy(&current_config, &backup_config)
189+
.context("Failed to backup GRUB config")?;
190+
}
191+
192+
crate::grubconfigs::install(&destdir, None, true)?;
193+
194+
// Remove the real config if it is symlink and will not
195+
// if /boot/grub2/grub.cfg is file
196+
if current_config != grub_config {
197+
println!("Removing {}", current_config);
198+
grub_config_dir.remove_file_optional(current_config.as_std_path())?;
199+
}
200+
201+
// Synchronize the filesystem containing /boot/grub2 to disk.
202+
let _ = grub_config_dir.syncfs();
203+
204+
Ok(())
205+
}
206+
145207
fn adopt_update(
146208
&self,
147209
rootcxt: &RootContext,
148210
update: &ContentMetadata,
211+
with_static_config: bool,
149212
) -> Result<Option<InstalledContent>> {
150213
let bios_devices = blockdev::find_colocated_bios_boot(&rootcxt.devices)?;
151214
let Some(meta) = self.query_adopt(&bios_devices)? else {
@@ -157,6 +220,17 @@ impl Component for Bios {
157220
log::debug!("Installed grub modules on {parent}");
158221
}
159222

223+
if with_static_config {
224+
// Install the static config if the OSTree bootloader is not set.
225+
if let Some(bootloader) = crate::ostreeutil::get_ostree_bootloader()? {
226+
println!(
227+
"ostree repo 'sysroot.bootloader' config option is currently set to: '{bootloader}'",
228+
);
229+
} else {
230+
println!("ostree repo 'sysroot.bootloader' config option is not set yet");
231+
self.migrate_static_grub_config(rootcxt.path.as_str(), &rootcxt.sysroot)?;
232+
};
233+
}
160234
Ok(Some(InstalledContent {
161235
meta: update.clone(),
162236
filetree: None,

src/bootupd.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,8 @@ pub(crate) fn install(
108108

109109
match configs.enabled_with_uuid() {
110110
Some(uuid) => {
111-
let self_bin_meta =
112-
std::fs::metadata("/proc/self/exe").context("Querying self meta")?;
113-
let self_meta = ContentMetadata {
114-
timestamp: self_bin_meta.modified()?.into(),
115-
version: crate_version!().into(),
116-
};
117-
state.static_configs = Some(self_meta);
111+
let meta = get_static_config_meta()?;
112+
state.static_configs = Some(meta);
118113
#[cfg(any(
119114
target_arch = "x86_64",
120115
target_arch = "aarch64",
@@ -139,6 +134,16 @@ pub(crate) fn install(
139134
Ok(())
140135
}
141136

137+
#[context("Get static config metadata")]
138+
fn get_static_config_meta() -> Result<ContentMetadata> {
139+
let self_bin_meta = std::fs::metadata("/proc/self/exe").context("Querying self meta")?;
140+
let self_meta = ContentMetadata {
141+
timestamp: self_bin_meta.modified()?.into(),
142+
version: crate_version!().into(),
143+
};
144+
Ok(self_meta)
145+
}
146+
142147
type Components = BTreeMap<&'static str, Box<dyn Component>>;
143148

144149
#[allow(clippy::box_default)]
@@ -261,6 +266,7 @@ pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdat
261266
pub(crate) fn adopt_and_update(
262267
name: &str,
263268
rootcxt: &RootContext,
269+
with_static_config: bool,
264270
) -> Result<Option<ContentMetadata>> {
265271
let sysroot = &rootcxt.sysroot;
266272
let mut state = SavedState::load_from_disk("/")?.unwrap_or_default();
@@ -274,15 +280,25 @@ pub(crate) fn adopt_and_update(
274280
let Some(update) = component.query_update(sysroot)? else {
275281
anyhow::bail!("Component {} has no available update", name);
276282
};
283+
277284
let sysroot = sysroot.try_clone()?;
278285
let mut state_guard =
279286
SavedState::acquire_write_lock(sysroot).context("Failed to acquire write lock")?;
280287

281288
let inst = component
282-
.adopt_update(&rootcxt, &update)
289+
.adopt_update(&rootcxt, &update, with_static_config)
283290
.context("Failed adopt and update")?;
284291
if let Some(inst) = inst {
285292
state.installed.insert(component.name().into(), inst);
293+
// Set static_configs metadata and save
294+
if with_static_config && state.static_configs.is_none() {
295+
let meta = get_static_config_meta()?;
296+
state.static_configs = Some(meta);
297+
// Set bootloader to none
298+
ostreeutil::set_ostree_bootloader("none")?;
299+
300+
println!("Static GRUB configuration has been adopted successfully.");
301+
}
286302
state_guard.update_state(&state)?;
287303
return Ok(Some(update));
288304
} else {
@@ -500,7 +516,7 @@ pub(crate) fn client_run_update() -> Result<()> {
500516
}
501517
for (name, adoptable) in status.adoptable.iter() {
502518
if adoptable.confident {
503-
if let Some(r) = adopt_and_update(name, &rootcxt)? {
519+
if let Some(r) = adopt_and_update(name, &rootcxt, false)? {
504520
println!("Adopted and updated: {}: {}", name, r.version);
505521
updated = true;
506522
}
@@ -514,14 +530,14 @@ pub(crate) fn client_run_update() -> Result<()> {
514530
Ok(())
515531
}
516532

517-
pub(crate) fn client_run_adopt_and_update() -> Result<()> {
533+
pub(crate) fn client_run_adopt_and_update(with_static_config: bool) -> Result<()> {
518534
let rootcxt = prep_before_update()?;
519535
let status: Status = status()?;
520536
if status.adoptable.is_empty() {
521537
println!("No components are adoptable.");
522538
} else {
523539
for (name, _) in status.adoptable.iter() {
524-
if let Some(r) = adopt_and_update(name, &rootcxt)? {
540+
if let Some(r) = adopt_and_update(name, &rootcxt, with_static_config)? {
525541
println!("Adopted and updated: {}: {}", name, r.version);
526542
}
527543
}

src/cli/bootupctl.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub enum CtlVerb {
5656
#[clap(name = "update", about = "Update all components")]
5757
Update,
5858
#[clap(name = "adopt-and-update", about = "Update all adoptable components")]
59-
AdoptAndUpdate,
59+
AdoptAndUpdate(AdoptAndUpdateOpts),
6060
#[clap(name = "validate", about = "Validate system state")]
6161
Validate,
6262
#[clap(
@@ -88,13 +88,20 @@ pub struct StatusOpts {
8888
json: bool,
8989
}
9090

91+
#[derive(Debug, Parser)]
92+
pub struct AdoptAndUpdateOpts {
93+
/// Install the static GRUB config files
94+
#[clap(long, action)]
95+
with_static_config: bool,
96+
}
97+
9198
impl CtlCommand {
9299
/// Run CLI application.
93100
pub fn run(self) -> Result<()> {
94101
match self.cmd {
95102
CtlVerb::Status(opts) => Self::run_status(opts),
96103
CtlVerb::Update => Self::run_update(),
97-
CtlVerb::AdoptAndUpdate => Self::run_adopt_and_update(),
104+
CtlVerb::AdoptAndUpdate(opts) => Self::run_adopt_and_update(opts),
98105
CtlVerb::Validate => Self::run_validate(),
99106
CtlVerb::Backend(CtlBackend::Generate(opts)) => {
100107
super::bootupd::DCommand::run_generate_meta(opts)
@@ -133,9 +140,9 @@ impl CtlCommand {
133140
}
134141

135142
/// Runner for `update` verb.
136-
fn run_adopt_and_update() -> Result<()> {
143+
fn run_adopt_and_update(opts: AdoptAndUpdateOpts) -> Result<()> {
137144
ensure_running_in_systemd()?;
138-
bootupd::client_run_adopt_and_update()
145+
bootupd::client_run_adopt_and_update(opts.with_static_config)
139146
}
140147

141148
/// Runner for `validate` verb.

src/component.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ pub(crate) trait Component {
3131
/// and "synthesize" content metadata from it.
3232
fn query_adopt(&self, devices: &Option<Vec<String>>) -> Result<Option<Adoptable>>;
3333

34+
// Backup the current grub config, and install static grub config from tree
35+
fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &openat::Dir) -> Result<()>;
36+
3437
/// Given an adoptable system and an update, perform the update.
3538
fn adopt_update(
3639
&self,
3740
rootcxt: &RootContext,
3841
update: &ContentMetadata,
42+
with_static_config: bool,
3943
) -> Result<Option<InstalledContent>>;
4044

4145
/// Implementation of `bootupd install` for a given component. This should

src/efi.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::bootupd::RootContext;
2424
use crate::model::*;
2525
use crate::ostreeutil;
2626
use crate::util;
27-
use crate::{blockdev, filetree};
27+
use crate::{blockdev, filetree, grubconfigs};
2828
use crate::{component::*, packagesystem};
2929

3030
/// Well-known paths to the ESP that may have been mounted external to us.
@@ -246,11 +246,41 @@ impl Component for Efi {
246246
crate::component::query_adopt_state()
247247
}
248248

249+
// Backup "/boot/efi/EFI/{vendor}/grub.cfg" to "/boot/efi/EFI/{vendor}/grub.cfg.bak"
250+
// Replace "/boot/efi/EFI/{vendor}/grub.cfg" with new static "grub.cfg"
251+
fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &openat::Dir) -> Result<()> {
252+
let sysroot =
253+
openat::Dir::open(sysroot_path).with_context(|| format!("Opening {sysroot_path}"))?;
254+
let Some(vendor) = self.get_efi_vendor(&sysroot)? else {
255+
anyhow::bail!("Failed to find efi vendor");
256+
};
257+
258+
// destdir is /boot/efi/EFI
259+
let efidir = destdir
260+
.sub_dir(&vendor)
261+
.with_context(|| format!("Opening EFI/{}", vendor))?;
262+
263+
if !efidir.exists(grubconfigs::GRUBCONFIG_BACKUP)? {
264+
println!("Creating a backup of the current GRUB config on EFI");
265+
efidir
266+
.copy_file(grubconfigs::GRUBCONFIG, grubconfigs::GRUBCONFIG_BACKUP)
267+
.context("Failed to backup GRUB config")?;
268+
}
269+
270+
grubconfigs::install(&sysroot, Some(&vendor), true)?;
271+
// Synchronize the filesystem containing /boot/efi/EFI/{vendor} to disk.
272+
// (ignore failures)
273+
let _ = efidir.syncfs();
274+
275+
Ok(())
276+
}
277+
249278
/// Given an adoptable system and an update, perform the update.
250279
fn adopt_update(
251280
&self,
252281
rootcxt: &RootContext,
253282
updatemeta: &ContentMetadata,
283+
with_static_config: bool,
254284
) -> Result<Option<InstalledContent>> {
255285
let esp_devices = blockdev::find_colocated_esps(&rootcxt.devices)?;
256286
let Some(meta) = self.query_adopt(&esp_devices)? else {
@@ -276,6 +306,19 @@ impl Component for Efi {
276306
filetree::apply_diff(&updated, &efidir, &diff, None)
277307
.context("applying filesystem changes")?;
278308

309+
// Backup current config and install static config
310+
if with_static_config {
311+
// Install the static config if the OSTree bootloader is not set.
312+
if let Some(bootloader) = crate::ostreeutil::get_ostree_bootloader()? {
313+
println!(
314+
"ostree repo 'sysroot.bootloader' config option is currently set to: '{bootloader}'",
315+
);
316+
} else {
317+
println!("ostree repo 'sysroot.bootloader' config option is not set yet");
318+
self.migrate_static_grub_config(rootcxt.path.as_str(), &efidir)?;
319+
};
320+
}
321+
279322
// Do the sync before unmount
280323
efidir.syncfs()?;
281324
drop(efidir);

src/filetree.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ use openat_ext::OpenatDirExt;
2929
target_arch = "riscv64"
3030
))]
3131
use openssl::hash::{Hasher, MessageDigest};
32+
#[cfg(any(
33+
target_arch = "x86_64",
34+
target_arch = "aarch64",
35+
target_arch = "riscv64"
36+
))]
3237
use rustix::fd::BorrowedFd;
3338
use serde::{Deserialize, Serialize};
3439
#[allow(unused_imports)]
@@ -40,8 +45,6 @@ use std::fmt::Display;
4045
target_arch = "riscv64"
4146
))]
4247
use std::os::unix::io::AsRawFd;
43-
use std::os::unix::process::CommandExt;
44-
use std::process::Command;
4548

4649
/// The prefix we apply to our temporary files.
4750
#[cfg(any(
@@ -339,6 +342,8 @@ pub(crate) struct ApplyUpdateOptions {
339342
))]
340343
fn copy_dir(root: &openat::Dir, src: &str, dst: &str) -> Result<()> {
341344
use bootc_utils::CommandRunExt;
345+
use std::os::unix::process::CommandExt;
346+
use std::process::Command;
342347

343348
let rootfd = unsafe { BorrowedFd::borrow_raw(root.as_raw_fd()) };
344349
unsafe {

src/grubconfigs.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use crate::freezethaw::fsfreeze_thaw_cycle;
1313
const GRUB2DIR: &str = "grub2";
1414
const CONFIGDIR: &str = "/usr/lib/bootupd/grub2-static";
1515
const DROPINDIR: &str = "configs.d";
16+
// The related grub files
1617
const GRUBENV: &str = "grubenv";
18+
pub(crate) const GRUBCONFIG: &str = "grub.cfg";
19+
pub(crate) const GRUBCONFIG_BACKUP: &str = "grub.cfg.backup";
1720

1821
/// Install the static GRUB config files.
1922
#[context("Installing static GRUB configs")]
@@ -109,6 +112,8 @@ pub(crate) fn install(
109112
println!("Installed: {target:?}");
110113
}
111114
fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
115+
} else {
116+
println!("Could not find /boot/efi/EFI when installing {target:?}");
112117
}
113118
}
114119

0 commit comments

Comments
 (0)