150 lines
3.6 KiB
Rust
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
|
|
}
|