diff --git a/src/bootupd.rs b/src/bootupd.rs index 10432f09..24cca7df 100644 --- a/src/bootupd.rs +++ b/src/bootupd.rs @@ -2,6 +2,7 @@ use crate::component::{Component, ValidationResult}; use crate::coreos; use crate::efi; use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedState, Status}; +use crate::util; use crate::{component, ipc}; use anyhow::{anyhow, Context, Result}; use serde::{Deserialize, Serialize}; @@ -93,6 +94,10 @@ pub(crate) enum ComponentUpdateResult { }, } +fn ensure_writable_boot() -> Result<()> { + util::ensure_writable_mount("/boot") +} + /// daemon implementation of component update pub(crate) fn update(name: &str) -> Result { let mut state = SavedState::load_from_disk("/")?.unwrap_or_default(); @@ -109,6 +114,8 @@ pub(crate) fn update(name: &str) -> Result { _ => return Ok(ComponentUpdateResult::AtLatestVersion), }; + ensure_writable_boot()?; + let mut pending_container = state.pending.take().unwrap_or_default(); let interrupted = pending_container.get(component.name()).cloned(); pending_container.insert(component.name().into(), update.clone()); @@ -140,6 +147,9 @@ pub(crate) fn adopt_and_update(name: &str) -> Result { if state.installed.get(name).is_some() { anyhow::bail!("Component {} is already installed", name); }; + + ensure_writable_boot()?; + let update = if let Some(update) = component.query_update(&sysroot)? { update } else { diff --git a/src/efi.rs b/src/efi.rs index 77aabb05..423eac61 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -94,6 +94,7 @@ impl Component for EFI { let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?; // For adoption, we should only touch files that we know about. let diff = updatef.relative_diff_to(&esp)?; + ensure_writable_efi()?; log::trace!("applying adoption diff: {}", &diff); filetree::apply_diff(&updated, &esp, &diff, None).context("applying filesystem changes")?; Ok(InstalledContent { @@ -153,6 +154,7 @@ impl Component for EFI { let destdir = openat::Dir::open(&Path::new("/").join(MOUNT_PATH).join("EFI")) .context("opening EFI dir")?; validate_esp(&destdir)?; + ensure_writable_efi()?; log::trace!("applying diff: {}", &diff); filetree::apply_diff(&updated, &destdir, &diff, None) .context("applying filesystem changes")?; @@ -280,3 +282,7 @@ fn validate_esp(dir: &openat::Dir) -> Result<()> { }; Ok(()) } + +fn ensure_writable_efi() -> Result<()> { + util::ensure_writable_mount(&Path::new("/").join(MOUNT_PATH)) +} diff --git a/src/util.rs b/src/util.rs index 5af899fa..1b8a64e9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use anyhow::{bail, Result}; use openat_ext::OpenatDirExt; +use std::path::Path; use std::process::Command; pub(crate) trait CommandRunExt { @@ -65,3 +66,20 @@ pub(crate) fn filenames(dir: &openat::Dir) -> Result> { } Ok(ret) } + +pub(crate) fn ensure_writable_mount>(p: P) -> Result<()> { + use nix::sys::statvfs; + let p = p.as_ref(); + let stat = statvfs::statvfs(p)?; + if !stat.flags().contains(statvfs::FsFlags::ST_RDONLY) { + return Ok(()); + } + let status = std::process::Command::new("mount") + .args(&["-o", "remount,rw"]) + .arg(p) + .status()?; + if !status.success() { + anyhow::bail!("Failed to remount {:?} writable", p); + } + Ok(()) +} diff --git a/tests/kola/test-bootupd b/tests/kola/test-bootupd index ab820787..ee0a6543 100755 --- a/tests/kola/test-bootupd +++ b/tests/kola/test-bootupd @@ -19,7 +19,9 @@ function cleanup () { fi } -bootefidir=/boot/efi/EFI +bootmount=/boot +bootefimount=${bootmount}/efi +bootefidir=${bootefimount}/EFI bootupdir=/usr/lib/bootupd/updates efiupdir=${bootupdir}/EFI ostbaseefi=/usr/lib/ostree-boot/efi/EFI @@ -115,6 +117,9 @@ bootupctl update | tee out.txt assert_file_has_content_literal out.txt 'No update available for any component' assert_not_file_has_content_literal out.txt 'Updated EFI' +# Ensure /boot and /boot/efi can be written to +mount -o remount,rw ${bootmount} +mount -o remount,rw ${bootefimount} echo "some additions" >> ${bootefidir}/${efisubdir}/shimx64.efi if bootupctl validate 2>err.txt; then fatal "unexpectedly passed validation"