|
1 | 1 | use std::ops::Deref; |
| 2 | +use std::process::Command; |
2 | 3 | use std::time::{SystemTime, UNIX_EPOCH}; |
3 | 4 |
|
4 | 5 | use anyhow::{anyhow, bail, Context, Result}; |
@@ -130,6 +131,47 @@ fn check_path_traversal(input: &str) -> Result<()> { |
130 | 131 | Ok(()) |
131 | 132 | } |
132 | 133 |
|
| 134 | +fn run_cmd(exec: &str, args: &[&str]) -> Result<Vec<u8>> { |
| 135 | + let mut cmd = Command::new(exec); |
| 136 | + cmd.args(args); |
| 137 | + let output = cmd |
| 138 | + .output() |
| 139 | + .with_context(|| format!("Failed to run command: {cmd:?}"))?; |
| 140 | + if !output.status.success() { |
| 141 | + let err = String::from_utf8_lossy(&output.stderr); |
| 142 | + bail!("Failed to run command {cmd:?}: {err}"); |
| 143 | + } |
| 144 | + Ok(output.stdout) |
| 145 | +} |
| 146 | + |
| 147 | +fn remove_all_qemu_bitmaps(disk_path: &str) -> Result<()> { |
| 148 | + // Get JSON info |
| 149 | + let output = run_cmd("qemu-img", &["info", "--output=json", disk_path])?; |
| 150 | + let info: serde_json::Value = serde_json::from_slice(&output)?; |
| 151 | + // Extract and remove bitmaps |
| 152 | + let Some(bitmaps) = info.pointer("/format-specific/data/bitmaps") else { |
| 153 | + return Ok(()); |
| 154 | + }; |
| 155 | + let bitmaps = bitmaps |
| 156 | + .as_array() |
| 157 | + .context("The bitmaps field is not an array")?; |
| 158 | + for bitmap in bitmaps { |
| 159 | + let Some(name) = bitmap["name"].as_str() else { |
| 160 | + info!("Invalid bitmap: {bitmap:?}"); |
| 161 | + continue; |
| 162 | + }; |
| 163 | + run_cmd("qemu-img", &["bitmap", "--remove", disk_path, name])?; |
| 164 | + info!("Removed bitmap {name} for {disk_path}"); |
| 165 | + } |
| 166 | + Ok(()) |
| 167 | +} |
| 168 | + |
| 169 | +fn resize_qcow2(disk_path: &str, size_gb: u32) -> Result<()> { |
| 170 | + let new_size_str = format!("{}G", size_gb); |
| 171 | + run_cmd("qemu-img", &["resize", disk_path, &new_size_str])?; |
| 172 | + Ok(()) |
| 173 | +} |
| 174 | + |
133 | 175 | impl RpcHandler { |
134 | 176 | fn resolve_gpus(&self, gpu_cfg: &rpc::GpuConfig) -> Result<GpuConfig> { |
135 | 177 | let gpus = resolve_gpus(gpu_cfg)?; |
@@ -391,26 +433,19 @@ impl VmmRpc for RpcHandler { |
391 | 433 | if let Some(image) = request.image { |
392 | 434 | manifest.image = image; |
393 | 435 | } |
394 | | - if let Some(disk_size) = request.disk_size { |
| 436 | + let resize_disk_to = request |
| 437 | + .disk_size |
| 438 | + .filter(|&disk_size| disk_size != manifest.disk_size); |
| 439 | + if let Some(disk_size) = resize_disk_to { |
395 | 440 | if disk_size < manifest.disk_size { |
396 | 441 | bail!("Cannot shrink disk size"); |
397 | 442 | } |
398 | | - manifest.disk_size = disk_size; |
399 | | - |
400 | | - // Run qemu-img resize to resize the disk |
401 | 443 | info!("Resizing disk to {}GB", disk_size); |
402 | 444 | let hda_path = vm_work_dir.hda_path(); |
403 | | - let new_size_str = format!("{}G", disk_size); |
404 | | - let output = std::process::Command::new("qemu-img") |
405 | | - .args(["resize", &hda_path.display().to_string(), &new_size_str]) |
406 | | - .output() |
407 | | - .context("Failed to resize disk")?; |
408 | | - if !output.status.success() { |
409 | | - bail!( |
410 | | - "Failed to resize disk: {}", |
411 | | - String::from_utf8_lossy(&output.stderr) |
412 | | - ); |
413 | | - } |
| 445 | + let hda_str = hda_path.display().to_string(); |
| 446 | + remove_all_qemu_bitmaps(&hda_str).context("Failed to remove qemu bitmaps")?; |
| 447 | + resize_qcow2(&hda_str, disk_size).context("Failed to resize disk")?; |
| 448 | + manifest.disk_size = disk_size; |
414 | 449 | } |
415 | 450 | vm_work_dir |
416 | 451 | .put_manifest(&manifest) |
|
0 commit comments