1use super::traits::ImageHandler;
2use crate::util::path_exists;
3use anyhow::{anyhow, Result};
4use kdam::{tqdm, BarExt};
5use std::{
6 fs::remove_file,
7 io::{Read, Write},
8 path::PathBuf,
9 process::{Command, Stdio},
10};
11
12pub const QEMU_IMG_PATH: &str = "qemu-img";
13pub const QEMU_IMG_DEFAULT_FORMAT: &str = "qcow2";
14
15pub fn qemu_img_name() -> String {
16 format!(
17 "qemu-{}.{}",
18 std::time::SystemTime::now()
19 .duration_since(std::time::UNIX_EPOCH)
20 .unwrap()
21 .as_secs(),
22 QEMU_IMG_DEFAULT_FORMAT,
23 )
24}
25
26#[derive(Debug, Clone)]
27pub struct QEmuImageHandler {
28 format: String,
29}
30
31impl Default for QEmuImageHandler {
32 fn default() -> Self {
33 Self {
34 format: QEMU_IMG_DEFAULT_FORMAT.to_string(),
35 }
36 }
37}
38
39impl ImageHandler for QEmuImageHandler {
40 fn import(&self, new_file: PathBuf, orig_file: PathBuf, format: String) -> Result<()> {
41 Command::new(QEMU_IMG_PATH)
42 .args(vec![
43 "convert",
44 "-f",
45 &format,
46 "-O",
47 &self.format,
48 orig_file.to_str().unwrap(),
49 new_file.to_str().unwrap(),
50 ])
51 .status()?;
52 Ok(())
53 }
54
55 fn create(&self, target: PathBuf, gbs: usize) -> Result<PathBuf> {
56 let filename = target.join(qemu_img_name());
57
58 if path_exists(filename.clone()) {
59 return Err(anyhow!(
60 "filename already exists; did you already create this vm?",
61 ));
62 }
63
64 let status = Command::new(QEMU_IMG_PATH)
65 .args(vec![
66 "create",
67 "-f",
68 &self.format,
69 filename.to_str().unwrap(),
70 &format!("{}G", gbs),
71 ])
72 .stderr(Stdio::null())
73 .stdout(Stdio::null())
74 .status();
75
76 match status {
77 Ok(st) => {
78 if st.success() {
79 Ok(filename)
80 } else {
81 Err(anyhow!(
82 "process exited with code: {}",
83 st.code().expect("unknown")
84 ))
85 }
86 }
87 Err(e) => Err(anyhow!(e)),
88 }
89 }
90
91 fn remove(&self, disk: PathBuf) -> Result<()> {
92 if !path_exists(disk.clone()) {
93 return Err(anyhow!("filename does not exist"));
94 }
95
96 Ok(remove_file(disk)?)
97 }
98
99 fn clone_image(&self, description: String, old: PathBuf, new: PathBuf) -> Result<()> {
100 let mut oldf = std::fs::OpenOptions::new();
101 oldf.read(true);
102 let mut oldf = oldf.open(old)?;
103 let mut newf = std::fs::OpenOptions::new();
104 newf.write(true);
105 newf.create_new(true);
106 let mut newf = newf.open(new.clone())?;
107 let mut buf = [0_u8; 4096];
108 let len = oldf.metadata()?.len();
109 let mut pb = tqdm!(total = len.try_into().unwrap());
110 pb.set_description(description);
111 pb.unit_scale = true;
112 pb.unit = "B".to_string();
113 for _ in 0..len / 4096 {
114 if let Ok(size) = oldf.read(&mut buf) {
115 pb.update(newf.write(&buf[..size])?)?;
116 }
117 }
118
119 newf.flush()?;
120 Ok(())
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use anyhow::Result;
128 use tempfile::tempdir;
129
130 #[test]
131 fn test_image() -> Result<()> {
132 let dir = tempdir()?;
133 let path = dir.into_path();
134
135 let image = QEmuImageHandler::default();
136 image.remove(image.create(path.clone(), 2)?)?;
137
138 Ok(())
139 }
140}