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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use crate::storage::{DirectoryStorageHandler, StorageHandler};
use anyhow::{anyhow, Result};
use std::{
    path::PathBuf,
    process::{Command, Stdio},
};

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

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

pub trait Imager {
    fn import(&self, name: &str, orig_file: PathBuf, format: &str) -> Result<()>;
    fn create(&self, name: &str, gbs: usize) -> Result<()>;
    fn clone(&self, orig: &str, new: &str) -> Result<()>;
}

pub struct QEmuImager {
    pub image_format: &'static str,
    storage: DirectoryStorageHandler,
}

impl QEmuImager {
    pub fn new(image_format: &'static str, storage: DirectoryStorageHandler) -> Self {
        QEmuImager {
            image_format,
            storage,
        }
    }
}

impl Default for QEmuImager {
    fn default() -> Self {
        Self::new(QEMU_IMG_DEFAULT_FORMAT, DirectoryStorageHandler::default())
    }
}

impl Imager for QEmuImager {
    fn import(&self, name: &str, orig_file: PathBuf, format: &str) -> Result<()> {
        if self.storage.vm_exists(name) {
            return Err(anyhow!(
                "file already exists, please delete the original vm",
            ));
        }

        match self.storage.vm_root(name) {
            Ok(path) => std::fs::create_dir_all(path)?,
            Err(e) => return Err(e),
        };

        let filename = qemu_img_name();

        let status = Command::new(&filename)
            .args(vec![
                "convert",
                "-f",
                format,
                "-O",
                QEMU_IMG_DEFAULT_FORMAT,
                orig_file.to_str().unwrap(),
                &self.storage.vm_path(name, &filename)?,
            ])
            .status();

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

    fn clone(&self, orig: &str, new: &str) -> Result<()> {
        if !self.storage.valid_filename(orig) || !self.storage.valid_filename(new) {
            return Err(anyhow!("vm names are invalid"));
        }

        for disk in self.storage.disk_list(orig)? {
            let filename = disk.file_name().unwrap().to_str().unwrap();
            if !self.storage.vm_path_exists(orig, filename) {
                return Err(anyhow!("original does not exist"));
            }

            if self.storage.vm_path_exists(new, filename) {
                return Err(anyhow!("target already exists"));
            }

            match std::fs::copy(
                self.storage.vm_path(orig, filename).unwrap(),
                self.storage.vm_path(new, filename).unwrap(),
            ) {
                Ok(_) => {}
                Err(e) => return Err(anyhow!(e)),
            }
        }

        Ok(())
    }

    fn create(&self, name: &str, gbs: usize) -> Result<()> {
        let filename = qemu_img_name();

        if self.storage.vm_path_exists(name, &filename) {
            return Err(anyhow!(
                "filename already exists; did you already create this vm?",
            ));
        }

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

            match status {
                Ok(st) => {
                    if st.success() {
                        return Ok(());
                    } else {
                        return Err(anyhow!(
                            "process exited with code: {}",
                            st.code().expect("unknown")
                        ));
                    }
                }
                Err(e) => return Err(anyhow!(e)),
            }
        } else {
            Err(anyhow!("could not derive path name from vm name"))
        }
    }
}