system_harness/
qemu.rs

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