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