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
use crate::{image::QEMU_IMG_NAME, launcher, storage::StorageHandler};
use anyhow::{anyhow, Result};
use std::path::PathBuf;

const QEMU_BIN_NAME: &str = "qemu-system-x86_64";

macro_rules! append_vec {
    ( $v:expr, $( $x:expr ),* ) => {
        {
            $(
                $v.push($x.into());
            )*
        }
    };
}

macro_rules! into_vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();

            $(
                temp_vec.push($x.into());
            )*

            temp_vec
        }
    };
}

pub struct Emulator {}

impl Emulator {
    fn hostfwd_rules(&self, vm_name: &str, rc: &launcher::RuntimeConfig) -> Result<String> {
        let config = rc.dsh.config(vm_name)?;
        config.check_ports()?;
        let mut res = String::new();
        for (host, guest) in config.ports {
            res += &format!(",hostfwd=tcp:127.0.0.1:{}-:{}", host, guest);
        }

        Ok(res)
    }

    fn cdrom_rules(&self, v: &mut Vec<String>, disk: Option<PathBuf>, index: u8) -> Result<()> {
        if let Some(cd) = disk {
            match std::fs::metadata(&cd) {
                Ok(_) => {
                    append_vec!(
                        v,
                        "-drive",
                        format!("file={},media=cdrom,index={}", cd.display(), index)
                    );
                }
                Err(e) => return Err(anyhow!("error locating cdrom file: {}", e)),
            }
        }
        Ok(())
    }

    fn display_rule(&self, v: &mut Vec<String>, headless: bool) {
        append_vec!(v, "-display");
        if !headless {
            append_vec!(v, "gtk");
        } else {
            append_vec!(v, "none");
        }
    }
}

impl launcher::Emulator for Emulator {
    fn args(&self, vm_name: &str, rc: &launcher::RuntimeConfig) -> Result<Vec<String>> {
        let config = rc.dsh.config(vm_name)?;
        if config.valid().is_ok() {
            if rc.dsh.vm_path_exists(vm_name, QEMU_IMG_NAME) {
                let img_path = rc.dsh.vm_path(vm_name, QEMU_IMG_NAME)?;
                let mon = rc.dsh.monitor_path(vm_name)?;

                let mut v: Vec<String> = into_vec![
                    "-nodefaults",
                    "-chardev",
                    format!("socket,server=on,wait=off,id=char0,path={}", mon.display()),
                    "-mon",
                    "chardev=char0,mode=control,pretty=on",
                    "-machine",
                    "accel=kvm",
                    "-vga",
                    config.machine.vga,
                    "-m",
                    format!("{}M", config.machine.memory),
                    "-cpu",
                    config.machine.cpu_type,
                    "-smp",
                    format!(
                        "cpus={},cores={},maxcpus={}",
                        config.machine.cpus, config.machine.cpus, config.machine.cpus
                    ),
                    "-drive",
                    format!(
                        "driver=qcow2,if={},file={},cache=none,media=disk,index=0",
                        config.machine.image_interface, img_path
                    ),
                    "-nic",
                    format!("user{}", self.hostfwd_rules(vm_name, rc)?)
                ];

                self.display_rule(&mut v, rc.headless);
                self.cdrom_rules(&mut v, rc.cdrom.clone(), 2)?;
                self.cdrom_rules(&mut v, rc.extra_disk.clone(), 3)?;

                Ok(v)
            } else {
                Err(anyhow!("vm image does not exist"))
            }
        } else {
            Err(anyhow!("vm configuration is invalid: {:?}", config.valid(),))
        }
    }

    fn bin(&self) -> Result<String> {
        Ok(QEMU_BIN_NAME.to_string())
    }
}