1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use super::traits::ImageHandler;
use crate::util::path_exists;
use anyhow::{anyhow, Result};
use kdam::{tqdm, BarExt};
use std::{
    fs::remove_file,
    io::{Read, Write},
    path::PathBuf,
    process::{Command, Stdio},
};

pub const QEMU_IMG_PATH: &str = "qemu-img";
pub const QEMU_IMG_DEFAULT_FORMAT: &str = "qcow2";

pub fn qemu_img_name() -> String {
    format!(
        "qemu-{}.{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs(),
        QEMU_IMG_DEFAULT_FORMAT,
    )
}

#[derive(Debug, Clone)]
pub struct QEmuImageHandler {
    format: String,
}

impl Default for QEmuImageHandler {
    fn default() -> Self {
        Self {
            format: QEMU_IMG_DEFAULT_FORMAT.to_string(),
        }
    }
}

impl ImageHandler for QEmuImageHandler {
    fn import(&self, new_file: PathBuf, orig_file: PathBuf, format: String) -> Result<()> {
        Command::new(QEMU_IMG_PATH)
            .args(vec![
                "convert",
                "-f",
                &format,
                "-O",
                &self.format,
                orig_file.to_str().unwrap(),
                new_file.to_str().unwrap(),
            ])
            .status()?;
        Ok(())
    }

    fn create(&self, target: PathBuf, gbs: usize) -> Result<()> {
        let filename = target.join(qemu_img_name());

        if path_exists(filename.clone()) {
            return Err(anyhow!(
                "filename already exists; did you already create this vm?",
            ));
        }

        let status = Command::new(QEMU_IMG_PATH)
            .args(vec![
                "create",
                "-f",
                &self.format,
                filename.to_str().unwrap(),
                &format!("{}G", gbs),
            ])
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status();

        match status {
            Ok(st) => {
                if st.success() {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "process exited with code: {}",
                        st.code().expect("unknown")
                    ))
                }
            }
            Err(e) => Err(anyhow!(e)),
        }
    }

    fn remove(&self, disk: PathBuf) -> Result<()> {
        if !path_exists(disk.clone()) {
            return Err(anyhow!("filename does not exist"));
        }

        Ok(remove_file(disk)?)
    }

    fn clone_image(&self, description: String, old: PathBuf, new: PathBuf) -> Result<()> {
        let mut oldf = std::fs::OpenOptions::new();
        oldf.read(true);
        let mut oldf = oldf.open(old)?;
        let mut newf = std::fs::OpenOptions::new();
        newf.write(true);
        newf.create_new(true);
        let mut newf = newf.open(new.clone())?;
        let mut buf = [0_u8; 4096];
        let len = oldf.metadata()?.len();
        let mut pb = tqdm!(total = len.try_into().unwrap());
        pb.set_description(description);
        pb.unit_scale = true;
        pb.unit = "B".to_string();
        for _ in 0..len / 4096 {
            if let Ok(size) = oldf.read(&mut buf) {
                pb.update(newf.write(&buf[..size])?)?;
            }
        }

        newf.flush()?;
        Ok(())
    }
}