use std::path::{Path, PathBuf};
use crate::{MicrosandboxError, MicrosandboxResult};
const BYTES_PER_MIB: u64 = 1024 * 1024;
pub(crate) enum UpperGrowError {
OverCapacity {
max_size_bytes: u64,
},
Other(String),
}
pub(crate) async fn grow_upper_to_mib(path: PathBuf, target_mib: u32) -> MicrosandboxResult<()> {
let metadata = tokio::fs::metadata(&path).await.map_err(|e| {
MicrosandboxError::Custom(format!("cannot grow upper.ext4 at {}: {e}", path.display()))
})?;
let target_bytes = u64::from(target_mib) * BYTES_PER_MIB;
if metadata.len() >= target_bytes {
return Ok(());
}
tokio::task::spawn_blocking(move || grow_upper_ext4(&path, target_bytes))
.await
.map_err(|e| MicrosandboxError::Runtime(format!("upper grow task failed: {e}")))?
.map_err(|err| match err {
UpperGrowError::OverCapacity { max_size_bytes } => {
MicrosandboxError::Custom(format!(
"cannot grow oci upper size to {target_mib} MiB: the existing upper.ext4 can grow to at most {} MiB; recreate the sandbox for a larger upper",
max_size_bytes / BYTES_PER_MIB
))
}
UpperGrowError::Other(message) => {
MicrosandboxError::Custom(format!("failed to grow upper.ext4: {message}"))
}
})
}
fn grow_upper_ext4(path: &Path, new_size_bytes: u64) -> Result<(), UpperGrowError> {
microsandbox_image::ext4::grow_image(path, new_size_bytes)
.map(|_outcome| ())
.map_err(map_ext4_error)
}
fn map_ext4_error(err: microsandbox_image::ext4::Ext4Error) -> UpperGrowError {
match err {
microsandbox_image::ext4::Ext4Error::ExceedsGdtCapacity { max_size_bytes, .. } => {
UpperGrowError::OverCapacity { max_size_bytes }
}
other => UpperGrowError::Other(other.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn grow_skips_files_already_at_or_beyond_target() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("upper.ext4");
let file = std::fs::File::create(&path).unwrap();
file.set_len(2 * BYTES_PER_MIB).unwrap();
grow_upper_to_mib(path.clone(), 2).await.unwrap();
grow_upper_to_mib(path, 1).await.unwrap();
}
#[tokio::test]
async fn grow_errors_when_upper_is_missing() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("missing.ext4");
let err = grow_upper_to_mib(path, 8).await.unwrap_err();
assert!(err.to_string().contains("cannot grow upper.ext4"));
}
}