Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/bootupd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use camino::{Utf8Path, Utf8PathBuf};
use clap::crate_version;
use fn_error_context::context;
use libc::mode_t;
use openat_ext::OpenatDirExt;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::BTreeMap;
Expand Down Expand Up @@ -212,6 +213,20 @@ pub(crate) fn get_components() -> Components {
get_components_impl(false)
}

/// Return available components
#[context("Get available components")]
pub(crate) fn get_available_components(sysroot: &openat::Dir) -> Result<Components> {
let mut avail = BTreeMap::new();

for (name, component) in get_components_impl(false) {
if crate::component::get_component_update(sysroot, component.as_ref())?.is_some() {
avail.insert(name, component);
}
}

Ok(avail)
}

pub(crate) fn generate_update_metadata(sysroot_path: &str) -> Result<()> {
// create bootupd update dir which will save component metadata files for both components
let updates_dir = Path::new(sysroot_path).join(crate::model::BOOTUPD_UPDATES_DIR);
Expand Down Expand Up @@ -639,6 +654,36 @@ pub(crate) fn client_run_validate() -> Result<()> {
Ok(())
}

pub(crate) fn client_run_remove_component(component_name: &str) -> Result<()> {
let sysroot = openat::Dir::open("/").context("opening sysroot directory /")?;

let components = get_available_components(&sysroot)?;

// Find the component (ignore ASCII case)
if let Some((name, component)) = components
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(component_name))
{
let comp_dirname = crate::component::component_updatedirname(component.as_ref());

// Construct the path relative to sysroot: BOOTUPD_UPDATES_DIR/<comp>.json
let path = comp_dirname.with_extension("json");

// Remove the file using the sysroot Dir handle
sysroot.remove_file_optional(&path).map_err(|e| {
if let Some(libc::EACCES) = e.raw_os_error() {
anyhow::anyhow!("Permission denied: Cannot remove component file at /{}. Try running with sudo.", path.display())
} else {
anyhow::anyhow!(e).context(format!("Failed to remove component file /{}", path.display()))
}
})?;
println!("Removed component '{}'", name);
} else {
anyhow::bail!("Could not find component '{}'", component_name);
}
Ok(())
}

#[context("Migrating to a static GRUB config")]
pub(crate) fn client_run_migrate_static_grub_config() -> Result<()> {
// Did we already complete the migration?
Expand Down
34 changes: 26 additions & 8 deletions src/cli/bootupctl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::bootupd;
use anyhow::Result;
use anyhow::{Context, Result};
use clap::Parser;
use log::LevelFilter;

Expand Down Expand Up @@ -65,6 +65,8 @@ pub enum CtlVerb {
about = "Migrate a system to a static GRUB config"
)]
MigrateStaticGrubConfig,
#[clap(name = "remove-component", about = "Remove component")]
RemoveComponent(RemoveComponentOpts),
}

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -95,6 +97,13 @@ pub struct AdoptAndUpdateOpts {
with_static_config: bool,
}

#[derive(Debug, Parser)]
pub struct RemoveComponentOpts {
/// Component name to be removed
#[clap(value_parser)]
component: String,
}

impl CtlCommand {
/// Run CLI application.
pub fn run(self) -> Result<()> {
Expand All @@ -103,6 +112,7 @@ impl CtlCommand {
CtlVerb::Update => Self::run_update(),
CtlVerb::AdoptAndUpdate(opts) => Self::run_adopt_and_update(opts),
CtlVerb::Validate => Self::run_validate(),
CtlVerb::RemoveComponent(opts) => Self::run_remove_component(opts),
CtlVerb::Backend(CtlBackend::Generate(opts)) => {
super::bootupd::DCommand::run_generate_meta(opts)
}
Expand Down Expand Up @@ -151,6 +161,11 @@ impl CtlCommand {
bootupd::client_run_validate()
}

/// Runner for `remove-component` verb.
fn run_remove_component(opts: RemoveComponentOpts) -> Result<()> {
bootupd::client_run_remove_component(&opts.component)
}

/// Runner for `migrate-static-grub-config` verb.
fn run_migrate_static_grub_config() -> Result<()> {
ensure_running_in_systemd()?;
Expand Down Expand Up @@ -214,20 +229,23 @@ fn ensure_running_in_systemd() -> Result<()> {

/// If running in container, just print the available payloads
fn run_status_in_container(json_format: bool) -> Result<()> {
let all_components = crate::bootupd::get_components();
if all_components.is_empty() {
return Ok(());
}
let avail: Vec<_> = all_components.keys().cloned().collect();
let sysroot = openat::Dir::open("/").context("opening sysroot directory /")?;

let avail: Vec<_> = crate::bootupd::get_available_components(&sysroot)?
.into_keys()
.collect();

if json_format {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let output: serde_json::Value = serde_json::json!({
let output = serde_json::json!({
"components": avail
});
serde_json::to_writer(&mut stdout, &output)?;
} else {
println!("Available components: {}", avail.join(" "));
if !avail.is_empty() {
println!("Available components: {}", avail.join(" "));
}
}
Ok(())
}
4 changes: 2 additions & 2 deletions tests/kola/test-bootupd
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ if [ -d /sys/firmware/efi ]; then
update_metadata_move EFI-bak EFI

# Should succeed if BIOS metadata is missing
rm -f ${bootupdir}/BIOS.json
bootupctl remove-component bios
bootupctl update | tee out.txt
assert_file_has_content out.txt 'Adopted and updated: EFI:'
assert_not_file_has_content out.txt 'Adopted and updated: BIOS:'
Expand All @@ -156,7 +156,7 @@ else
update_metadata_move BIOS-bak BIOS

# Should succeed if EFI metadata is missing
rm -f ${bootupdir}/EFI.json
bootupctl remove-component efi
bootupctl update | tee out.txt
assert_file_has_content out.txt 'Adopted and updated: BIOS:'
assert_not_file_has_content out.txt 'Adopted and updated: EFI:'
Expand Down
46 changes: 35 additions & 11 deletions tests/tests/bootupctl-status-in-bootc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,46 @@ if [ ! -d "/sysroot/ostree/repo/" ]; then
exit 100
fi

components_text_x86_64='Available components: BIOS EFI'
components_json_x86_64='{"components":["BIOS","EFI"]}'

components_text_aarch64='Available components: EFI'
components_json_aarch64='{"components":["EFI"]}'

none_components_json='{"components":[]}'

# check if running in container
if [ "$container" ] || [ -f /run/.containerenv ] || [ -f /.dockerenv ]; then
arch="$(uname --machine)"
if [[ "${arch}" == "x86_64" ]]; then
components_text='Available components: BIOS EFI'
components_json='{"components":["BIOS","EFI"]}'
else
# Assume aarch64 for now
components_text='Available components: EFI'
components_json='{"components":["EFI"]}'
output_text=$(bootupctl status | tr -d '\r')
output_json=$(bootupctl status --json)

if [ "${arch}" == "x86_64" ]; then
[ "${components_text_x86_64}" == "${output_text}" ]
[ "${components_json_x86_64}" == "${output_json}" ]
# test with no BIOS.json
bootupctl remove-component bios
output_text=$(bootupctl status | tr -d '\r')
output_json=$(bootupctl status --json)
fi

output=$(bootupctl status | tr -d '\r')
[ "${components_text}" == "${output}" ]
output=$(bootupctl status --json)
[ "${components_json}" == "${output}" ]
if [ "${arch}" == "x86_64" ] || [ "${arch}" == "aarch64" ]; then
[ "${components_text_aarch64}" == "${output_text}" ]
[ "${components_json_aarch64}" == "${output_json}" ]
fi

# test with no components
bootupctl remove-component efi
output_text=$(bootupctl status | tr -d '\r')
output_json=$(bootupctl status --json)
[ -z "${output_text}" ]
[ "${none_components_json}" == "${output_json}" ]

# remove none existing component
if bootupctl remove-component test 2>err.txt; then
echo "unexpectedly passed remove none existing component"
exit 1
fi
else
echo "Skip running as not in container"
fi
Loading