Skip to main content

qemu_command_builder/args/
serial.rs

1use bon::Builder;
2use proptest_derive::Arbitrary;
3use std::path::PathBuf;
4use std::str::FromStr;
5
6use crate::common::OnOff;
7use crate::to_command::{ToArg, ToCommand};
8
9/// A QEMU special character device target used by options such as
10/// `-serial`, `-parallel`, `-monitor`, and `-qmp`.
11#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
12pub struct VC {
13    is_pixel: bool,
14    w: usize,
15    h: usize,
16}
17
18#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
19pub struct Udp {
20    remote_host: Option<String>,
21    remote_port: u16,
22    src_ip: Option<String>,
23    src_port: Option<u16>,
24}
25
26#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
27pub struct Tcp {
28    host: String,
29    port: u16,
30    server: Option<OnOff>,
31    wait: Option<OnOff>,
32    nodelay: Option<OnOff>,
33    reconnect_ms: Option<usize>,
34}
35
36#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
37pub struct Telnet {
38    host: String,
39    port: u16,
40    server: Option<OnOff>,
41    wait: Option<OnOff>,
42    nodelay: Option<OnOff>,
43}
44
45#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
46pub struct Websocket {
47    host: String,
48    port: u16,
49    server: Option<OnOff>,
50    wait: Option<OnOff>,
51    nodelay: Option<OnOff>,
52}
53
54#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
55pub struct Unix {
56    path: PathBuf,
57    server: Option<OnOff>,
58    wait: Option<OnOff>,
59    reconnect_ms: Option<usize>,
60}
61
62#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
63pub enum SpecialDevice {
64    VC(Option<VC>),
65    Pty(Option<PathBuf>),
66    None,
67    Null,
68    Chardev(String),
69    Dev(String),
70    Parport(usize),
71    File(PathBuf),
72    Stdio,
73    Pipe(PathBuf),
74    Com(usize),
75    Udp(Udp),
76    Tcp(Tcp),
77    Telnet(Telnet),
78    Websocket(Websocket),
79    Unix(Unix),
80    Mon(String),
81    Braille,
82    Msmouse,
83}
84
85impl ToCommand for SpecialDevice {
86    fn to_args(&self) -> Vec<String> {
87        let mut args = vec![];
88
89        match self {
90            SpecialDevice::VC(vc) => {
91                if let Some(vc) = vc {
92                    if vc.is_pixel {
93                        args.push(format!("vc:{}x{}", vc.w, vc.h));
94                    } else {
95                        args.push(format!("vc:{}Cx{}C", vc.w, vc.h));
96                    }
97                } else {
98                    args.push("vc".to_string());
99                }
100            }
101            SpecialDevice::Pty(pty) => {
102                if let Some(pty) = pty {
103                    args.push(format!("pty:{}", pty.display()));
104                } else {
105                    args.push("pty".to_string());
106                }
107            }
108            SpecialDevice::None => {
109                args.push("none".to_string());
110            }
111            SpecialDevice::Null => {
112                args.push("null".to_string());
113            }
114            SpecialDevice::Chardev(chardev) => {
115                args.push(format!("chardev:{}", chardev));
116            }
117            SpecialDevice::Dev(dev) => {
118                args.push(format!("/dev/{}", dev));
119            }
120            SpecialDevice::Parport(parport) => {
121                args.push(format!("/dev/parport{}", parport));
122            }
123            SpecialDevice::File(file) => {
124                args.push(format!("file:{}", file.display()));
125            }
126            SpecialDevice::Stdio => {
127                args.push("stdio".to_string());
128            }
129            SpecialDevice::Pipe(pipe) => {
130                args.push(format!("pipe:{}", pipe.display()));
131            }
132            SpecialDevice::Com(com) => {
133                args.push(format!("COM{}", com));
134            }
135            SpecialDevice::Udp(udp) => {
136                let mut udpargs = vec![];
137
138                udpargs.push("udp".to_string());
139                if let Some(remote_host) = &udp.remote_host {
140                    udpargs.push(remote_host.to_string());
141                }
142                udpargs.push(format!("{}", udp.remote_port));
143                if let Some(src_ip) = &udp.src_ip {
144                    udpargs.push(format!("@{}", src_ip));
145                }
146                if let Some(src_port) = &udp.src_port {
147                    udpargs.push(format!("{}", src_port));
148                }
149                args.push(udpargs.join(":"));
150            }
151            SpecialDevice::Tcp(tcp) => {
152                let mut tcpargs = vec![];
153
154                tcpargs.push(format!("tcp:{}:{}", tcp.host, tcp.port));
155                if let Some(server) = &tcp.server {
156                    tcpargs.push(format!("server={}", server.to_arg()));
157                }
158                if let Some(wait) = &tcp.wait {
159                    tcpargs.push(format!("wait={}", wait.to_arg()));
160                }
161                if let Some(nodelay) = &tcp.nodelay {
162                    tcpargs.push(format!("nodelay={}", nodelay.to_arg()));
163                }
164                if let Some(reconnect_ms) = &tcp.reconnect_ms {
165                    tcpargs.push(format!("reconnect-ms={}", reconnect_ms));
166                }
167                args.push(tcpargs.join(","));
168            }
169            SpecialDevice::Telnet(telnet) => {
170                let mut telnetargs = vec![];
171
172                telnetargs.push(format!("telnet:{}:{}", telnet.host, telnet.port));
173                if let Some(server) = &telnet.server {
174                    telnetargs.push(format!("server={}", server.to_arg()));
175                }
176                if let Some(wait) = &telnet.wait {
177                    telnetargs.push(format!("wait={}", wait.to_arg()));
178                }
179                if let Some(nodelay) = &telnet.nodelay {
180                    telnetargs.push(format!("nodelay={}", nodelay.to_arg()));
181                }
182                args.push(telnetargs.join(","));
183            }
184            SpecialDevice::Websocket(ws) => {
185                let mut wsargs = vec![];
186
187                wsargs.push(format!("websocket:{}:{}", ws.host, ws.port));
188                if let Some(server) = &ws.server {
189                    wsargs.push(format!("server={}", server.to_arg()));
190                }
191                if let Some(wait) = &ws.wait {
192                    wsargs.push(format!("wait={}", wait.to_arg()));
193                }
194                if let Some(nodelay) = &ws.nodelay {
195                    wsargs.push(format!("nodelay={}", nodelay.to_arg()));
196                }
197                args.push(wsargs.join(","));
198            }
199            SpecialDevice::Unix(uds) => {
200                let mut udsargs = vec![];
201
202                udsargs.push(format!("unix:{}", uds.path.display()));
203                if let Some(server) = &uds.server {
204                    udsargs.push(format!("server={}", server.to_arg()));
205                }
206                if let Some(wait) = &uds.wait {
207                    udsargs.push(format!("wait={}", wait.to_arg()));
208                }
209                if let Some(reconnect_ms) = &uds.reconnect_ms {
210                    udsargs.push(format!("reconnect-ms={}", reconnect_ms));
211                }
212                args.push(udsargs.join(","));
213            }
214            SpecialDevice::Mon(mon) => {
215                args.push(format!("mon:{}", mon));
216            }
217            SpecialDevice::Braille => {
218                args.push("braille".to_string());
219            }
220            SpecialDevice::Msmouse => {
221                args.push("msmouse".to_string());
222            }
223        }
224        args
225    }
226}
227
228impl FromStr for SpecialDevice {
229    type Err = String;
230
231    fn from_str(s: &str) -> Result<Self, Self::Err> {
232        if let Some(rest) = s.strip_prefix("vc:") {
233            let (w, h, is_pixel) = if let Some((w, h)) = rest.split_once('x') {
234                if let Some(h) = h.strip_suffix('C') { (w.trim_end_matches('C'), h, false) } else { (w, h, true) }
235            } else {
236                return Err(format!("invalid vc geometry: {rest}"));
237            };
238            return Ok(Self::VC(Some(VC {
239                is_pixel,
240                w: w.parse::<usize>().map_err(|e| e.to_string())?,
241                h: h.parse::<usize>().map_err(|e| e.to_string())?,
242            })));
243        }
244        if s == "vc" {
245            return Ok(Self::VC(None));
246        }
247        if s == "none" {
248            return Ok(Self::None);
249        }
250        if s == "null" {
251            return Ok(Self::Null);
252        }
253        if s == "stdio" {
254            return Ok(Self::Stdio);
255        }
256        if s == "braille" {
257            return Ok(Self::Braille);
258        }
259        if s == "msmouse" {
260            return Ok(Self::Msmouse);
261        }
262        if let Some(chardev) = s.strip_prefix("chardev:") {
263            return Ok(Self::Chardev(chardev.to_string()));
264        }
265        if let Some(mon) = s.strip_prefix("mon:") {
266            return Ok(Self::Mon(mon.to_string()));
267        }
268        if let Some(rest) = s.strip_prefix("udp:") {
269            let mut split = rest.split('@');
270            let remote = split.next().ok_or_else(|| "invalid udp endpoint".to_string())?;
271            let local = split.next();
272            let remote_parts = remote.split(':').collect::<Vec<_>>();
273            let (remote_host, remote_port) = match remote_parts.as_slice() {
274                [port] => (None, port.parse::<u16>().map_err(|e| e.to_string())?),
275                [host, port] => (Some((*host).to_string()), port.parse::<u16>().map_err(|e| e.to_string())?),
276                _ => return Err(format!("invalid udp endpoint: {remote}")),
277            };
278            let src = if let Some(local) = local {
279                let (src_ip, src_port) = local.rsplit_once(':').ok_or_else(|| format!("invalid udp source endpoint: {local}"))?;
280                (Some(src_ip.to_string()), Some(src_port.parse::<u16>().map_err(|e| e.to_string())?))
281            } else {
282                (None, None)
283            };
284            return Ok(Self::Udp(Udp {
285                remote_host,
286                remote_port,
287                src_ip: src.0,
288                src_port: src.1,
289            }));
290        }
291        if let Some(rest) = s.strip_prefix("tcp:") {
292            let mut parts = rest.split(',');
293            let endpoint = parts.next().ok_or_else(|| "invalid tcp endpoint".to_string())?;
294            let (host, port) = endpoint.rsplit_once(':').ok_or_else(|| format!("invalid tcp endpoint: {endpoint}"))?;
295            let mut server = None;
296            let mut wait = None;
297            let mut nodelay = None;
298            let mut reconnect_ms = None;
299            for part in parts {
300                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid tcp option: {part}"))?;
301                match key {
302                    "server" => server = Some(value.parse::<OnOff>().map_err(|_| format!("invalid server value: {value}"))?),
303                    "wait" => wait = Some(value.parse::<OnOff>().map_err(|_| format!("invalid wait value: {value}"))?),
304                    "nodelay" => nodelay = Some(value.parse::<OnOff>().map_err(|_| format!("invalid nodelay value: {value}"))?),
305                    "reconnect-ms" => reconnect_ms = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
306                    other => return Err(format!("unsupported tcp option: {other}")),
307                }
308            }
309            return Ok(Self::Tcp(Tcp {
310                host: host.to_string(),
311                port: port.parse::<u16>().map_err(|e| e.to_string())?,
312                server,
313                wait,
314                nodelay,
315                reconnect_ms,
316            }));
317        }
318        if let Some(rest) = s.strip_prefix("telnet:") {
319            let mut parts = rest.split(',');
320            let endpoint = parts.next().ok_or_else(|| "invalid telnet endpoint".to_string())?;
321            let (host, port) = endpoint.rsplit_once(':').ok_or_else(|| format!("invalid telnet endpoint: {endpoint}"))?;
322            let mut server = None;
323            let mut wait = None;
324            let mut nodelay = None;
325            for part in parts {
326                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid telnet option: {part}"))?;
327                match key {
328                    "server" => server = Some(value.parse::<OnOff>().map_err(|_| format!("invalid server value: {value}"))?),
329                    "wait" => wait = Some(value.parse::<OnOff>().map_err(|_| format!("invalid wait value: {value}"))?),
330                    "nodelay" => nodelay = Some(value.parse::<OnOff>().map_err(|_| format!("invalid nodelay value: {value}"))?),
331                    other => return Err(format!("unsupported telnet option: {other}")),
332                }
333            }
334            return Ok(Self::Telnet(Telnet {
335                host: host.to_string(),
336                port: port.parse::<u16>().map_err(|e| e.to_string())?,
337                server,
338                wait,
339                nodelay,
340            }));
341        }
342        if let Some(rest) = s.strip_prefix("websocket:") {
343            let mut parts = rest.split(',');
344            let endpoint = parts.next().ok_or_else(|| "invalid websocket endpoint".to_string())?;
345            let (host, port) = endpoint.rsplit_once(':').ok_or_else(|| format!("invalid websocket endpoint: {endpoint}"))?;
346            let mut server = None;
347            let mut wait = None;
348            let mut nodelay = None;
349            for part in parts {
350                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid websocket option: {part}"))?;
351                match key {
352                    "server" => server = Some(value.parse::<OnOff>().map_err(|_| format!("invalid server value: {value}"))?),
353                    "wait" => wait = Some(value.parse::<OnOff>().map_err(|_| format!("invalid wait value: {value}"))?),
354                    "nodelay" => nodelay = Some(value.parse::<OnOff>().map_err(|_| format!("invalid nodelay value: {value}"))?),
355                    other => return Err(format!("unsupported websocket option: {other}")),
356                }
357            }
358            return Ok(Self::Websocket(Websocket {
359                host: host.to_string(),
360                port: port.parse::<u16>().map_err(|e| e.to_string())?,
361                server,
362                wait,
363                nodelay,
364            }));
365        }
366        if let Some(rest) = s.strip_prefix("unix:") {
367            let mut parts = rest.split(',');
368            let path = PathBuf::from(parts.next().ok_or_else(|| "invalid unix endpoint".to_string())?);
369            let mut server = None;
370            let mut wait = None;
371            let mut reconnect_ms = None;
372            for part in parts {
373                let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid unix option: {part}"))?;
374                match key {
375                    "server" => server = Some(value.parse::<OnOff>().map_err(|_| format!("invalid server value: {value}"))?),
376                    "wait" => wait = Some(value.parse::<OnOff>().map_err(|_| format!("invalid wait value: {value}"))?),
377                    "reconnect-ms" => reconnect_ms = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
378                    other => return Err(format!("unsupported unix option: {other}")),
379                }
380            }
381            return Ok(Self::Unix(Unix { path, server, wait, reconnect_ms }));
382        }
383        if let Some(path) = s.strip_prefix("file:") {
384            return Ok(Self::File(PathBuf::from(path)));
385        }
386        if let Some(path) = s.strip_prefix("pipe:") {
387            return Ok(Self::Pipe(PathBuf::from(path)));
388        }
389        if let Some(path) = s.strip_prefix("pty:") {
390            return Ok(Self::Pty(Some(PathBuf::from(path))));
391        }
392        if s == "pty" {
393            return Ok(Self::Pty(None));
394        }
395        if let Some(dev) = s.strip_prefix("/dev/parport") {
396            let index = dev.parse::<usize>().map_err(|e| e.to_string())?;
397            return Ok(Self::Parport(index));
398        }
399        if let Some(dev) = s.strip_prefix("/dev/") {
400            return Ok(Self::Dev(dev.to_string()));
401        }
402        if let Some(port) = s.strip_prefix("COM") {
403            return Ok(Self::Com(port.parse::<usize>().map_err(|e| e.to_string())?));
404        }
405
406        Err(format!("unsupported special device: {s}"))
407    }
408}