nazrin/nzrd/src/img.rs

150 lines
3.6 KiB
Rust

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<String>,
}
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<std::io::Error> 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<S>(message: S) -> Self
where
S: AsRef<str>,
{
Self {
message: message.as_ref().to_owned(),
command_output: None,
}
}
fn with_output<S>(message: S, output: S) -> Self
where
S: AsRef<str>,
{
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<PathBuf, ImgError> {
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<F, Fut>(func: F) -> Result<Qcow2Img, ImgError>
where
F: FnOnce(Command, &Path) -> Fut,
Fut: Future<Output = std::io::Result<std::process::Output>>,
{
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<Qcow2Img, ImgError> {
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<P>(from: P, size: SizeInfo) -> Result<Qcow2Img, ImgError>
where
P: AsRef<Path>,
{
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
}