From 2636c09e2d9ab26708e1dc1527f9a803da6518fa Mon Sep 17 00:00:00 2001 From: GatewayJ <835269233@qq.com> Date: Thu, 14 May 2026 21:35:45 +0800 Subject: [PATCH 1/2] fix qcow2 backing and container status checks --- src/boxlite/src/disk/qcow2.rs | 63 +++++++++++++++++++++++++++++++- src/guest/src/container/start.rs | 10 ++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/boxlite/src/disk/qcow2.rs b/src/boxlite/src/disk/qcow2.rs index 98a46ef72..0b784096a 100644 --- a/src/boxlite/src/disk/qcow2.rs +++ b/src/boxlite/src/disk/qcow2.rs @@ -3,7 +3,7 @@ //! Creates and manages qcow2 disk images for Box block devices. use std::fs::OpenOptions; -use std::io::Write; +use std::io::{ErrorKind, Read, Write}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -1040,6 +1040,25 @@ pub fn read_backing_chain(path: &Path) -> Vec { let mut current = path.to_path_buf(); for _ in 0..MAX_BACKING_CHAIN_DEPTH { + match has_qcow2_magic(¤t) { + Ok(true) => {} + Ok(false) => { + tracing::debug!( + path = %current.display(), + "Reached non-qcow2 backing file; ending backing chain" + ); + break; + } + Err(e) => { + tracing::warn!( + path = %current.display(), + error = %e, + "Failed to inspect qcow2 backing path — returning partial chain" + ); + break; + } + } + match read_backing_file_path(¤t) { Ok(Some(backing)) => { let backing_path = PathBuf::from(backing); @@ -1064,6 +1083,26 @@ pub fn read_backing_chain(path: &Path) -> Vec { chain } +/// Return true when `path` starts with the QCOW2 magic (`QFI\xfb`). +/// +/// Raw backing files are valid terminal nodes in a qcow2 backing chain, so a +/// non-qcow2 magic is not an error here. +fn has_qcow2_magic(path: &Path) -> BoxliteResult { + let mut file = std::fs::File::open(path) + .map_err(|e| BoxliteError::Storage(format!("Failed to open {}: {}", path.display(), e)))?; + + let mut magic_buf = [0u8; 4]; + match file.read_exact(&mut magic_buf) { + Ok(()) => Ok(u32::from_be_bytes(magic_buf) == QCOW2_MAGIC), + Err(e) if e.kind() == ErrorKind::UnexpectedEof => Ok(false), + Err(e) => Err(BoxliteError::Storage(format!( + "Failed to read magic from {}: {}", + path.display(), + e + ))), + } +} + /// Check if `target` appears in the backing chain of `chain_root`. /// /// Walks the full qcow2 backing chain from `chain_root` and returns `true` @@ -1404,6 +1443,28 @@ mod tests { assert!(result.is_err()); } + #[test] + fn test_has_qcow2_magic_treats_raw_disk_as_chain_terminal() { + let dir = TempDir::new().unwrap(); + let raw_path = dir.path().join("base.ext4"); + std::fs::write(&raw_path, vec![0u8; 4096]).unwrap(); + + assert!(!has_qcow2_magic(&raw_path).unwrap()); + } + + #[test] + fn test_read_backing_chain_stops_at_raw_backing_file() { + let dir = TempDir::new().unwrap(); + let qcow2_path = dir.path().join("child.qcow2"); + let raw_path = dir.path().join("base.ext4"); + std::fs::write(&raw_path, vec![0u8; 4096]).unwrap(); + write_qcow2_with_backing(&qcow2_path, Some(&raw_path.to_string_lossy())); + + let chain = read_backing_chain(&qcow2_path); + + assert_eq!(chain, vec![raw_path]); + } + #[test] fn test_backing_format_as_str() { assert_eq!(BackingFormat::Raw.as_str(), "raw"); diff --git a/src/guest/src/container/start.rs b/src/guest/src/container/start.rs index 29a9450ea..fd7e17fba 100644 --- a/src/guest/src/container/start.rs +++ b/src/guest/src/container/start.rs @@ -274,7 +274,7 @@ pub(crate) fn cleanup_bundle_directory(bundle_path: &std::path::Path) { pub(crate) fn load_container_status( container_state_path: &Path, ) -> BoxliteResult { - let container = LibContainer::load(container_state_path.to_path_buf()).map_err(|e| { + let mut container = LibContainer::load(container_state_path.to_path_buf()).map_err(|e| { BoxliteError::Internal(format!( "Failed to load container from {}: {}", container_state_path.display(), @@ -282,5 +282,13 @@ pub(crate) fn load_container_status( )) })?; + container.refresh_status().map_err(|e| { + BoxliteError::Internal(format!( + "Failed to refresh container status from {}: {}", + container_state_path.display(), + e + )) + })?; + Ok(container.status()) } From 8308c59c59e93810eb33626a8e3303e1a0136e88 Mon Sep 17 00:00:00 2001 From: GatewayJ <835269233@qq.com> Date: Wed, 20 May 2026 18:30:01 +0800 Subject: [PATCH 2/2] test: cover stale container status refresh --- src/guest/src/container/start.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/guest/src/container/start.rs b/src/guest/src/container/start.rs index fd7e17fba..df7d2d6da 100644 --- a/src/guest/src/container/start.rs +++ b/src/guest/src/container/start.rs @@ -292,3 +292,25 @@ pub(crate) fn load_container_status( Ok(container.status()) } + +#[cfg(test)] +mod tests { + use super::*; + use libcontainer::container::{ContainerStatus, State}; + + #[test] + fn load_container_status_refreshes_stale_persisted_status() { + let dir = tempfile::TempDir::new().expect("create temp dir"); + let state = State::new( + "test-container", + ContainerStatus::Stopped, + Some(i32::try_from(std::process::id()).expect("current pid fits in i32")), + dir.path().to_path_buf(), + ); + state.save(dir.path()).expect("save libcontainer state"); + + let status = load_container_status(dir.path()).expect("load container status"); + + assert_eq!(status, ContainerStatus::Running); + } +}