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#[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_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
116pub 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}