Skip to content
Open
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
63 changes: 62 additions & 1 deletion src/boxlite/src/disk/qcow2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1040,6 +1040,25 @@ pub fn read_backing_chain(path: &Path) -> Vec<PathBuf> {
let mut current = path.to_path_buf();

for _ in 0..MAX_BACKING_CHAIN_DEPTH {
match has_qcow2_magic(&current) {
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(&current) {
Ok(Some(backing)) => {
let backing_path = PathBuf::from(backing);
Expand All @@ -1064,6 +1083,26 @@ pub fn read_backing_chain(path: &Path) -> Vec<PathBuf> {
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<bool> {
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`
Expand Down Expand Up @@ -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");
Expand Down
32 changes: 31 additions & 1 deletion src/guest/src/container/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,43 @@ pub(crate) fn cleanup_bundle_directory(bundle_path: &std::path::Path) {
pub(crate) fn load_container_status(
container_state_path: &Path,
) -> BoxliteResult<libcontainer::container::ContainerStatus> {
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(),
e
))
})?;

container.refresh_status().map_err(|e| {
Comment thread
GatewayJ marked this conversation as resolved.
BoxliteError::Internal(format!(
"Failed to refresh container status from {}: {}",
container_state_path.display(),
e
))
})?;

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);
}
}
Loading