system_harness/
qemu.rs

1use crate::{Error, ErrorKind, EventPublisher, EventSubscriber, Key, Status, SystemHarness, SystemTerminal};
2use cmdstruct::Command;
3use serde::{Deserialize, Serialize};
4use std::io::{Read, Write};
5use std::os::unix::net::UnixStream;
6use std::process::Child;
7
8mod args;
9
10mod models;
11use models::*;
12
13mod qmp;
14use qmp::QmpStream;
15
16fn qemu_system_bin(config: &QemuSystemConfig) -> String {
17    format!("qemu-system-{}", config.arch)
18}
19
20/// A configuration for running QEMU
21///
22/// This config can be serialized and deserialized using
23/// serde.
24#[derive(Clone, Command, Serialize, Deserialize)]
25#[command(executable_fn = qemu_system_bin)]
26pub struct QemuSystemConfig {
27    arch: String,
28
29    #[arg(option = "-boot")]
30    boot: Option<Boot>,
31
32    #[arg(option = "-cpu")]
33    cpu: Option<String>,
34
35    #[arg(option = "-machine")]
36    machine: Option<Machine>,
37
38    #[arg(option = "-smp")]
39    smp: Option<Smp>,
40
41    #[arg(option = "-accel")]
42    accel: Option<String>,
43
44    #[arg(option = "-bios")]
45    bios: Option<String>,
46
47    #[arg(option = "-m")]
48    memory: Option<usize>,
49
50    #[arg(option = "-cdrom")]
51    cdrom: Option<String>,
52
53    #[arg(option = "-hda")]
54    hda: Option<String>,
55
56    #[arg(option = "-hdb")]
57    hdb: Option<String>,
58
59    #[arg(option = "-device")]
60    device: Option<Vec<Device>>,
61
62    #[arg(option = "-chardev")]
63    chardev: Option<Vec<Backend<CharDev>>>,
64
65    #[arg(option = "-netdev")]
66    netdev: Option<Vec<Backend<NetDev>>>,
67
68    #[arg(option = "-blockdev")]
69    blockdev: Option<Vec<BlockDev>>,
70
71    /// Extra QEMU args
72    extra_args: Option<Vec<String>>
73}
74
75impl QemuSystemConfig {
76    pub fn build(&self) -> Result<QemuSystem, Error> {
77        let mut command = self.command();
78
79        command.arg("-nographic");
80        command.args(["-qmp", "unix:qmp.sock,server=on,wait=off"]);
81        command.args(["-serial", "unix:serial.sock,server=on,wait=off"]);
82
83        if let Some(extra_args) = &self.extra_args {
84            command.args(extra_args);
85        }
86
87        log::trace!("Starting system...");
88        let mut process = command.spawn()?;
89
90        log::trace!("Connecting to QMP socket...");
91        let mut qmp_socket = None;
92        while process.try_wait()?.is_none() && qmp_socket.is_none() {
93            qmp_socket = UnixStream::connect("qmp.sock").ok();
94        }
95        let qmp = QmpStream::new(qmp_socket.unwrap())?;
96        log::trace!("Connecting to serial socket...");
97        let serial = UnixStream::connect("serial.sock")?;
98        log::trace!("System ready.");
99        Ok(QemuSystem {
100            process,
101            serial,
102            qmp,
103        })
104    }
105}
106
107/// A running QEMU system
108pub struct QemuSystem {
109    process: Child,
110    serial: UnixStream,
111    qmp: QmpStream,
112}
113
114pub struct QemuSystemTerminal {
115    serial: UnixStream,
116    qmp: QmpStream
117}
118
119impl Read for QemuSystemTerminal {
120    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
121        self.serial.read(buf)
122    }
123}
124
125impl Write for QemuSystemTerminal {
126    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
127        self.serial.write(buf)
128    }
129
130    fn flush(&mut self) -> std::io::Result<()> {
131        self.serial.flush()
132    }
133}
134
135impl SystemTerminal for QemuSystemTerminal {
136
137    fn send_key(&mut self, key: Key) -> Result<(), Error> {
138        self.qmp
139            .send_command(qmp::QmpCommand::SendKey(qmp::KeyCommand {
140                keys: vec![key.into()],
141            }))
142            .map(|_| ())
143    }
144
145}
146
147impl SystemHarness for QemuSystem {
148
149    type Terminal = QemuSystemTerminal;
150
151    fn terminal(&self) -> Result<Self::Terminal, Error> {
152        let serial = self.serial.try_clone()?;
153        let qmp = self.qmp.try_clone()?;
154        Ok(QemuSystemTerminal {
155            serial,
156            qmp
157        })
158    }
159
160
161    fn running(&mut self) -> Result<bool, Error> {
162        self.process
163            .try_wait()
164            .map(|status| status == None)
165            .map_err(|err| err.into())
166    }
167
168    fn pause(&mut self) -> Result<(), Error> {
169        self.qmp.send_command(qmp::QmpCommand::Stop).map(|_| ())
170    }
171
172    fn resume(&mut self) -> Result<(), Error> {
173        self.qmp.send_command(qmp::QmpCommand::Cont).map(|_| ())
174    }
175
176    fn shutdown(&mut self) -> Result<(), Error> {
177        self.qmp
178            .send_command(qmp::QmpCommand::SystemPowerdown)
179            .map(|_| ())
180    }
181
182    fn status(&mut self) -> Result<Status, Error> {
183        self.qmp
184            .send_command(qmp::QmpCommand::QueryStatus)
185            .and_then(|ret| match ret {
186                qmp::QmpReturn::StatusInfo(status) => status.try_into(),
187                _ => Err(Error::new(
188                    ErrorKind::HarnessError,
189                    format!("Unexpected return"),
190                )),
191            })
192    }
193}
194
195impl EventPublisher for QemuSystem {
196    fn subscribe(&mut self, subscriber: impl EventSubscriber) -> Result<(), Error> {
197        self.qmp.subscribe(subscriber)
198    }
199}
200
201impl Drop for QemuSystem {
202    fn drop(&mut self) {
203        if let Ok(true) = self.running() {
204            log::trace!("Stopping running system...");
205            if let Err(err) = self.qmp.send_command(qmp::QmpCommand::Quit) {
206                log::warn!("Error quiting system: {err}");
207            }
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214
215    use super::*;
216
217    #[test]
218    fn json_config() {
219        const JSON_CONFIG: &'static str = include_str!("../tests/data/qemu-config.json");
220        let config: QemuSystemConfig = serde_json::from_str(JSON_CONFIG).unwrap();
221        let command = config.command();
222        assert_eq!("qemu-system-i386", command.get_program());
223        assert_eq!(
224            vec!["-machine", "type=q35", "-m", "512",
225                "-device", "driver=virtio-blk,drive=f1",
226                "-blockdev", "driver=file,node-name=f1,filename=tests/data/test.raw"],
227            command.get_args().collect::<Vec<_>>()
228        );
229    }
230}