emu_cli/
image.rs

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}