From db6768804223401385d08d227f689c95cdc7affd Mon Sep 17 00:00:00 2001 From: Wei Shi Date: Wed, 11 Feb 2026 16:29:29 +0800 Subject: [PATCH] upgrade: Add pre-flight disk space check Extends PR #1245's approach to all bootc upgrade modes that download layers (default, --apply, --download-only). Moves check_disk_space() to deploy.rs for reuse by both install and upgrade operations. This prevents wasted bandwidth and provides immediate feedback when insufficient disk space is available, matching the install behavior. Related: BIFROST-1088 Assisted-by: AI Signed-off-by: Wei Shi --- crates/lib/src/deploy.rs | 29 +++++++++++++++++++++++++++++ crates/lib/src/install.rs | 31 ++++++------------------------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/crates/lib/src/deploy.rs b/crates/lib/src/deploy.rs index 4fcb1d73d..efdd1a07f 100644 --- a/crates/lib/src/deploy.rs +++ b/crates/lib/src/deploy.rs @@ -21,6 +21,7 @@ use ostree_ext::ostree::Deployment; use ostree_ext::ostree::{self, Sysroot}; use ostree_ext::sysroot::SysrootLock; use ostree_ext::tokio_util::spawn_blocking_cancellable_flatten; +use std::os::fd::AsFd; use crate::progress_jsonl::{Event, ProgressWriter, SubTaskBytes, SubTaskStep}; use crate::spec::ImageReference; @@ -361,6 +362,31 @@ pub(crate) async fn prune_container_store(sysroot: &Storage) -> Result<()> { Ok(()) } +/// Verify there is sufficient disk space to pull an image. +/// +/// This checks the available space on the filesystem containing the OSTree repository +/// against the number of bytes that need to be fetched for the image. +pub(crate) fn check_disk_space( + repo_fd: impl AsFd, + image_meta: &PreparedImportMeta, + imgref: &ImageReference, +) -> Result<()> { + let stat = rustix::fs::fstatvfs(repo_fd)?; + let bytes_avail: u64 = stat.f_bsize * stat.f_bavail; + tracing::trace!("bytes_avail: {bytes_avail}"); + + if image_meta.bytes_to_fetch > bytes_avail { + anyhow::bail!( + "Insufficient free space for {image} (available: {bytes_avail} required: {bytes_to_fetch})", + bytes_avail = ostree_ext::glib::format_size(bytes_avail), + bytes_to_fetch = ostree_ext::glib::format_size(image_meta.bytes_to_fetch), + image = imgref.image, + ); + } + + Ok(()) +} + pub(crate) struct PreparedImportMeta { pub imp: ImageImporter, pub prep: Box, @@ -658,6 +684,9 @@ pub(crate) async fn pull( Ok(existing) } PreparedPullResult::Ready(prepared_image_meta) => { + // Check disk space before attempting to pull + check_disk_space(repo.dfd_borrow(), &prepared_image_meta, imgref)?; + // Log that we're pulling a new image const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0"; tracing::info!( diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index a056d03b7..1a9b21539 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -190,9 +190,7 @@ use self::baseline::InstallBlockDeviceOpts; use crate::bootc_composefs::{boot::setup_composefs_boot, repo::initialize_composefs_repository}; use crate::boundimage::{BoundImage, ResolvedBoundImage}; use crate::containerenv::ContainerExecutionInfo; -use crate::deploy::{ - MergeState, PreparedImportMeta, PreparedPullResult, prepare_for_pull, pull_from_prepared, -}; +use crate::deploy::{MergeState, PreparedPullResult, prepare_for_pull, pull_from_prepared}; use crate::lsm; use crate::progress_jsonl::ProgressWriter; use crate::spec::{Bootloader, ImageReference}; @@ -1011,27 +1009,6 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result Ok((storage, has_ostree)) } -fn check_disk_space( - repo_fd: impl AsFd, - image_meta: &PreparedImportMeta, - imgref: &ImageReference, -) -> Result<()> { - let stat = rustix::fs::fstatvfs(repo_fd)?; - let bytes_avail: u64 = stat.f_bsize * stat.f_bavail; - tracing::trace!("bytes_avail: {bytes_avail}"); - - if image_meta.bytes_to_fetch > bytes_avail { - anyhow::bail!( - "Insufficient free space for {image} (available: {bytes_avail} required: {bytes_to_fetch})", - bytes_avail = ostree_ext::glib::format_size(bytes_avail), - bytes_to_fetch = ostree_ext::glib::format_size(image_meta.bytes_to_fetch), - image = imgref.image, - ); - } - - Ok(()) -} - #[context("Creating ostree deployment")] async fn install_container( state: &State, @@ -1099,7 +1076,11 @@ async fn install_container( let pulled_image = match prepared { PreparedPullResult::AlreadyPresent(existing) => existing, PreparedPullResult::Ready(image_meta) => { - check_disk_space(root_setup.physical_root.as_fd(), &image_meta, &spec_imgref)?; + crate::deploy::check_disk_space( + root_setup.physical_root.as_fd(), + &image_meta, + &spec_imgref, + )?; pull_from_prepared(&spec_imgref, false, ProgressWriter::default(), *image_meta).await? } };