use std::{ fs::File, ops::Deref, path::{Path, PathBuf}, }; use std::future::Future; use tempdir::TempDir; use tokio::process::Command; use crate::ctrl::virtxml::SizeInfo; #[derive(Debug)] pub struct ImgError { message: String, command_output: Option, } impl std::fmt::Display for ImgError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(output) = &self.command_output { write!(f, "{}\n output from command: {}", self.message, output) } else { write!(f, "{}", self.message) } } } impl From for ImgError { fn from(value: std::io::Error) -> Self { Self { message: format!("IO Error: {}", value), command_output: None, } } } impl std::error::Error for ImgError {} impl ImgError { fn new(message: S) -> Self where S: AsRef, { Self { message: message.as_ref().to_owned(), command_output: None, } } fn with_output(message: S, output: S) -> Self where S: AsRef, { Self { message: message.as_ref().to_owned(), command_output: Some(output.as_ref().to_owned()), } } } /// Locates where qemu-img is on the server system fn qemu_img_path() -> Result { if let Ok(path_var) = std::env::var("PATH") { for path in path_var.split(':') { let qemu_img = Path::new(path).join("qemu-img"); if qemu_img.exists() { return Ok(qemu_img); } } Err(ImgError::new("couldn't find qemu-img in PATH; you may want to define where qemu-img is in nazrin.conf")) } else { Err(ImgError::new( "can't read PATH; you may want to define where qemu-img is in nazrin.conf", )) } } // =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= pub struct Qcow2Img { _img_dir: TempDir, img_file: File, } impl Deref for Qcow2Img { type Target = File; fn deref(&self) -> &Self::Target { &self.img_file } } fn cmd_err(err: std::io::Error) -> ImgError { ImgError::new(format!("failed to invoke qemu-img: {}", err)) } async fn run_qemu_img(func: F) -> Result where F: FnOnce(Command, &Path) -> Fut, Fut: Future>, { let qi_path = qemu_img_path()?; let img_dir = TempDir::new("nazrin")?; let img_path = img_dir.path().join("img"); let qi_out = func(Command::new(&qi_path), &img_path) .await .map_err(cmd_err)?; if !qi_out.status.success() { Err(ImgError::with_output( "qemu-img failed", &String::from_utf8_lossy(&qi_out.stderr), )) } else { Ok(Qcow2Img { _img_dir: img_dir, img_file: File::open(&img_path) .expect("Couldn't open image file after qemu-img wrote to it"), }) } } pub async fn create_qcow2(size: SizeInfo) -> Result { run_qemu_img(|mut cmd, img_path| { cmd.arg("create") .arg("-f") .arg("qcow2") .arg(img_path) .arg(size.qemu_style()) .output() }) .await } pub async fn clone_qcow2

(from: P, size: SizeInfo) -> Result where P: AsRef, { run_qemu_img(|mut cmd, img_path| { std::fs::copy(&from, img_path).expect("Copy failed"); cmd.arg("resize") .arg(img_path) .arg(size.qemu_style()) .output() }) .await }