Skip to main content

qemu_command_builder/args/
chardev.rs

1use crate::parsers::ARG_CHARDEV;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use bon::Builder;
6use proptest_derive::Arbitrary;
7
8use crate::common::OnOff;
9use crate::parsers::DELIM_COMMA;
10use crate::to_command::{ToArg, ToCommand};
11
12/// A QEMU `-chardev` backend.
13///
14/// The parser supports the canonical backend forms that this crate renders for
15/// common QEMU character device backends such as `socket`, `stdio`, `file`,
16/// `pipe`, `pty`, `hub`, and related simple backends.
17#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
18pub struct CharNull {
19    id: String,
20    mux: Option<OnOff>,
21    logfile: Option<PathBuf>,
22    logappend: Option<OnOff>,
23}
24
25#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
26pub struct CharSocketTcp {
27    id: String,
28    host: Option<String>,
29    port: u16,
30    to: Option<u16>,
31    ipv4: Option<OnOff>,
32    ipv6: Option<OnOff>,
33    nodelay: Option<OnOff>,
34    server: Option<OnOff>,
35    wait: Option<OnOff>,
36    telnet: Option<OnOff>,
37    websocket: Option<OnOff>,
38    reconnect_ms: Option<usize>,
39    mux: Option<OnOff>,
40    logfile: Option<PathBuf>,
41    logappend: Option<OnOff>,
42    tls_creds: Option<String>,
43    tls_authz: Option<String>,
44}
45
46#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
47pub struct CharSocketUds {
48    #[builder(into)]
49    id: String,
50    path: PathBuf,
51    server: Option<OnOff>,
52    wait: Option<OnOff>,
53    telnet: Option<OnOff>,
54    websocket: Option<OnOff>,
55    reconnect_ms: Option<usize>,
56    mux: Option<OnOff>,
57    logfile: Option<PathBuf>,
58    logappend: Option<OnOff>,
59    abstract_opt: Option<OnOff>,
60    tight: Option<OnOff>,
61}
62
63#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
64pub enum CharSocket {
65    Tcp(CharSocketTcp),
66    Uds(CharSocketUds),
67}
68
69#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
70pub struct CharUdp {
71    id: String,
72    host: Option<String>,
73    port: u16,
74    localaddr: Option<String>,
75    localport: Option<u16>,
76    ipv4: Option<OnOff>,
77    ipv6: Option<OnOff>,
78    mux: Option<OnOff>,
79    logfile: Option<PathBuf>,
80    logappend: Option<OnOff>,
81}
82
83#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
84pub struct CharMsMouse {
85    id: String,
86    mux: Option<OnOff>,
87    logfile: Option<PathBuf>,
88    logappend: Option<OnOff>,
89}
90
91#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
92pub struct CharHub {
93    id: String,
94    chardevs: Option<Vec<(usize, String)>>,
95}
96#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
97pub struct CharVc {
98    id: String,
99    width: Option<usize>,
100    height: Option<usize>,
101    cols: Option<usize>,
102    rows: Option<usize>,
103    mux: Option<OnOff>,
104    logfile: Option<PathBuf>,
105    logappend: Option<OnOff>,
106}
107
108#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
109pub struct CharRingBuf {
110    id: String,
111    size: Option<usize>,
112    logfile: Option<PathBuf>,
113    logappend: Option<OnOff>,
114}
115#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
116pub struct CharFile {
117    id: String,
118    path: PathBuf,
119    input_path: Option<PathBuf>,
120    mux: Option<OnOff>,
121    logfile: Option<PathBuf>,
122    logappend: Option<OnOff>,
123}
124
125#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
126pub struct CharPipe {
127    id: String,
128    path: PathBuf,
129    mux: Option<OnOff>,
130    logfile: Option<PathBuf>,
131    logappend: Option<OnOff>,
132}
133
134#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
135pub struct CharWin32Console {
136    id: String,
137    mux: Option<OnOff>,
138    logfile: Option<PathBuf>,
139    logappend: Option<OnOff>,
140}
141
142#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
143pub struct CharWin32Serial {
144    id: String,
145    path: PathBuf,
146    mux: Option<OnOff>,
147    logfile: Option<PathBuf>,
148    logappend: Option<OnOff>,
149}
150
151#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
152pub struct CharPty {
153    id: String,
154    path: Option<PathBuf>,
155    mux: Option<OnOff>,
156    logfile: Option<PathBuf>,
157    logappend: Option<OnOff>,
158}
159
160#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
161pub struct CharStdio {
162    id: String,
163    mux: Option<OnOff>,
164    signal: Option<OnOff>,
165    logfile: Option<PathBuf>,
166    logappend: Option<OnOff>,
167}
168
169#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
170pub struct CharBraille {
171    id: String,
172    mux: Option<OnOff>,
173    logfile: Option<PathBuf>,
174    logappend: Option<OnOff>,
175}
176#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
177pub struct CharSerial {
178    id: String,
179    path: PathBuf,
180    mux: Option<OnOff>,
181    logfile: Option<PathBuf>,
182    logappend: Option<OnOff>,
183}
184#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
185pub struct CharParallel {
186    id: String,
187    path: PathBuf,
188    mux: Option<OnOff>,
189    logfile: Option<PathBuf>,
190    logappend: Option<OnOff>,
191}
192
193#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
194pub struct CharSpice {
195    id: String,
196    name: String,
197    debug: Option<String>,
198    logfile: Option<PathBuf>,
199    logappend: Option<OnOff>,
200}
201
202#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
203pub enum CharDev {
204    Null(CharNull),
205    Socket(CharSocket),
206    Udp(CharUdp),
207    MsMouse(CharMsMouse),
208    Hub(CharHub),
209    Vc(CharVc),
210    RingBuf(CharRingBuf),
211    File(CharFile),
212    Pipe(CharPipe),
213    Win32Console(CharWin32Console),
214    Win32Serial(CharWin32Serial),
215    Pty(CharPty),
216    Stdio(CharStdio),
217    Braille(CharBraille),
218    Serial(CharSerial),
219    Parallel(CharParallel),
220    SpiceVmc(CharSpice),
221    SpicePort(CharSpice),
222}
223
224impl CharDev {
225    /// Returns the configured `id=` for the chardev backend.
226    pub fn id(&self) -> &str {
227        match self {
228            CharDev::Null(n) => &n.id,
229            CharDev::Socket(s) => match s {
230                CharSocket::Tcp(t) => &t.id,
231                CharSocket::Uds(u) => &u.id,
232            },
233            CharDev::Udp(u) => &u.id,
234            CharDev::MsMouse(m) => &m.id,
235            CharDev::Hub(h) => &h.id,
236            CharDev::Vc(v) => &v.id,
237            CharDev::RingBuf(r) => &r.id,
238            CharDev::File(f) => &f.id,
239            CharDev::Pipe(p) => &p.id,
240            CharDev::Win32Console(w) => &w.id,
241            CharDev::Win32Serial(ws) => &ws.id,
242            CharDev::Pty(p) => &p.id,
243            CharDev::Stdio(s) => &s.id,
244            CharDev::Braille(b) => &b.id,
245            CharDev::Serial(s) => &s.id,
246            CharDev::Parallel(p) => &p.id,
247            CharDev::SpiceVmc(s) => &s.id,
248            CharDev::SpicePort(s) => &s.id,
249        }
250    }
251}
252
253impl ToCommand for CharDev {
254    fn command(&self) -> String {
255        ARG_CHARDEV.to_string()
256    }
257
258    fn to_args(&self) -> Vec<String> {
259        let mut args = vec![];
260        match self {
261            CharDev::Null(null) => {
262                args.push("null".to_string());
263                args.push(format!("id={}", null.id));
264                if let Some(mux) = &null.mux {
265                    args.push(format!("mux={}", mux.to_arg()));
266                }
267                if let Some(logfile) = &null.logfile {
268                    args.push(format!("logfile={}", logfile.display()));
269                }
270                if let Some(logappend) = &null.logappend {
271                    args.push(format!("logappend={}", logappend.to_arg()));
272                }
273            }
274            CharDev::Socket(socket) => match socket {
275                CharSocket::Tcp(tcp) => {
276                    args.push("socket".to_string());
277                    args.push(format!("id={}", tcp.id));
278                    if let Some(host) = &tcp.host {
279                        args.push(format!("host={}", host));
280                    }
281                    args.push(format!("port={}", tcp.port));
282                    if let Some(to) = &tcp.to {
283                        args.push(format!("to={}", to));
284                    }
285                    if let Some(ipv4) = &tcp.ipv4 {
286                        args.push(format!("ipv4={}", ipv4.to_arg()));
287                    }
288                    if let Some(ipv6) = &tcp.ipv6 {
289                        args.push(format!("ipv6={}", ipv6.to_arg()));
290                    }
291                    if let Some(nodelay) = &tcp.nodelay {
292                        args.push(format!("nodelay={}", nodelay.to_arg()));
293                    }
294                    if let Some(server) = &tcp.server {
295                        args.push(format!("server={}", server.to_arg()));
296                    }
297                    if let Some(wait) = &tcp.wait {
298                        args.push(format!("wait={}", wait.to_arg()));
299                    }
300                    if let Some(telnet) = &tcp.telnet {
301                        args.push(format!("telnet={}", telnet.to_arg()));
302                    }
303                    if let Some(websocket) = &tcp.websocket {
304                        args.push(format!("websocket={}", websocket.to_arg()));
305                    }
306                    if let Some(reconnect_ms) = &tcp.reconnect_ms {
307                        args.push(format!("reconnect-ms={}", reconnect_ms));
308                    }
309                    if let Some(mux) = &tcp.mux {
310                        args.push(format!("mux={}", mux.to_arg()));
311                    }
312                    if let Some(logfile) = &tcp.logfile {
313                        args.push(format!("logfile={}", logfile.display()));
314                    }
315                    if let Some(logappend) = &tcp.logappend {
316                        args.push(format!("logappend={}", logappend.to_arg()));
317                    }
318                    if let Some(tls_creds) = &tcp.tls_creds {
319                        args.push(format!("tls-creds={}", tls_creds));
320                    }
321                    if let Some(tls_authz) = &tcp.tls_authz {
322                        args.push(format!("tls-authz={}", tls_authz));
323                    }
324                }
325                CharSocket::Uds(uds) => {
326                    args.push("socket".to_string());
327                    args.push(format!("id={}", uds.id));
328                    args.push(format!("path={}", uds.path.display()));
329
330                    if let Some(server) = &uds.server {
331                        args.push(format!("server={}", server.to_arg()));
332                    }
333                    if let Some(wait) = &uds.wait {
334                        args.push(format!("wait={}", wait.to_arg()));
335                    }
336                    if let Some(telnet) = &uds.telnet {
337                        args.push(format!("telnet={}", telnet.to_arg()));
338                    }
339                    if let Some(websocket) = &uds.websocket {
340                        args.push(format!("websocket={}", websocket.to_arg()));
341                    }
342                    if let Some(reconnect_ms) = &uds.reconnect_ms {
343                        args.push(format!("reconnect-ms={}", reconnect_ms));
344                    }
345                    if let Some(mux) = &uds.mux {
346                        args.push(format!("mux={}", mux.to_arg()));
347                    }
348                    if let Some(logfile) = &uds.logfile {
349                        args.push(format!("logfile={}", logfile.display()));
350                    }
351                    if let Some(logappend) = &uds.logappend {
352                        args.push(format!("logappend={}", logappend.to_arg()));
353                    }
354                    if let Some(abstract_opt) = &uds.abstract_opt {
355                        args.push(format!("abstract={}", abstract_opt.to_arg()));
356                    }
357                    if let Some(tight) = &uds.tight {
358                        args.push(format!("tight={}", tight.to_arg()));
359                    }
360                }
361            },
362            CharDev::Udp(udp) => {
363                args.push("udp".to_string());
364                args.push(format!("id={}", udp.id));
365
366                if let Some(host) = &udp.host {
367                    args.push(format!("host={}", host));
368                }
369                args.push(format!("port={}", udp.port));
370                if let Some(localaddr) = &udp.localaddr {
371                    args.push(format!("localaddr={}", localaddr));
372                }
373                if let Some(localport) = &udp.localport {
374                    args.push(format!("localport={}", localport));
375                }
376                if let Some(ipv4) = &udp.ipv4 {
377                    args.push(format!("ipv4={}", ipv4.to_arg()));
378                }
379                if let Some(ipv6) = &udp.ipv6 {
380                    args.push(format!("ipv6={}", ipv6.to_arg()));
381                }
382                if let Some(mux) = &udp.mux {
383                    args.push(format!("mux={}", mux.to_arg()));
384                }
385                if let Some(logfile) = &udp.logfile {
386                    args.push(format!("logfile={}", logfile.display()));
387                }
388                if let Some(logappend) = &udp.logappend {
389                    args.push(format!("logappend={}", logappend.to_arg()));
390                }
391            }
392            CharDev::MsMouse(msmouse) => {
393                args.push("msmouse".to_string());
394                args.push(format!("id={}", msmouse.id));
395                if let Some(mux) = &msmouse.mux {
396                    args.push(format!("mux={}", mux.to_arg()));
397                }
398                if let Some(logfile) = &msmouse.logfile {
399                    args.push(format!("logfile={}", logfile.display()));
400                }
401                if let Some(logappend) = &msmouse.logappend {
402                    args.push(format!("logappend={}", logappend.to_arg()));
403                }
404            }
405            CharDev::Hub(hub) => {
406                args.push("hub".to_string());
407                args.push(format!("id={}", hub.id));
408                if let Some(chardevs) = &hub.chardevs {
409                    for (n, chardev) in chardevs {
410                        args.push(format!("chardevs.{}={}", n, chardev));
411                    }
412                }
413            }
414            CharDev::Vc(vc) => {
415                args.push("vc".to_string());
416                args.push(format!("id={}", vc.id));
417                if let Some(width) = &vc.width {
418                    args.push(format!("width={}", width));
419                }
420                if let Some(height) = &vc.height {
421                    args.push(format!("height={}", height));
422                }
423                if let Some(cols) = &vc.cols {
424                    args.push(format!("cols={}", cols));
425                }
426                if let Some(rows) = &vc.rows {
427                    args.push(format!("rows={}", rows));
428                }
429                if let Some(mux) = &vc.mux {
430                    args.push(format!("mux={}", mux.to_arg()));
431                }
432                if let Some(logvc) = &vc.logfile {
433                    args.push(format!("logfile={}", logvc.display()));
434                }
435                if let Some(logappend) = &vc.logappend {
436                    args.push(format!("logappend={}", logappend.to_arg()));
437                }
438            }
439            CharDev::RingBuf(ringbuf) => {
440                args.push("ringbuf".to_string());
441                args.push(format!("id={}", ringbuf.id));
442                if let Some(size) = &ringbuf.size {
443                    args.push(format!("size={}", size));
444                }
445                if let Some(logfile) = &ringbuf.logfile {
446                    args.push(format!("logfile={}", logfile.display()));
447                }
448                if let Some(logappend) = &ringbuf.logappend {
449                    args.push(format!("logappend={}", logappend.to_arg()));
450                }
451            }
452            CharDev::File(file) => {
453                args.push("file".to_string());
454                args.push(format!("id={}", file.id));
455                args.push(format!("path={}", file.path.display()));
456                if let Some(input_path) = &file.input_path {
457                    args.push(format!("input-path={}", input_path.display()));
458                }
459                if let Some(mux) = &file.mux {
460                    args.push(format!("mux={}", mux.to_arg()));
461                }
462                if let Some(logfile) = &file.logfile {
463                    args.push(format!("logfile={}", logfile.display()));
464                }
465                if let Some(logappend) = &file.logappend {
466                    args.push(format!("logappend={}", logappend.to_arg()));
467                }
468            }
469            CharDev::Pipe(pipe) => {
470                args.push("pipe".to_string());
471                args.push(format!("id={}", pipe.id));
472                args.push(format!("path={}", pipe.path.display()));
473                if let Some(mux) = &pipe.mux {
474                    args.push(format!("mux={}", mux.to_arg()));
475                }
476                if let Some(logpipe) = &pipe.logfile {
477                    args.push(format!("logfile={}", logpipe.display()));
478                }
479                if let Some(logappend) = &pipe.logappend {
480                    args.push(format!("logappend={}", logappend.to_arg()));
481                }
482            }
483            CharDev::Win32Console(console) => {
484                args.push("console".to_string());
485                args.push(format!("id={}", console.id));
486                if let Some(mux) = &console.mux {
487                    args.push(format!("mux={}", mux.to_arg()));
488                }
489                if let Some(logconsole) = &console.logfile {
490                    args.push(format!("logfile={}", logconsole.display()));
491                }
492                if let Some(logappend) = &console.logappend {
493                    args.push(format!("logappend={}", logappend.to_arg()));
494                }
495            }
496            CharDev::Win32Serial(serial) => {
497                args.push("serial".to_string());
498                args.push(format!("id={}", serial.id));
499                args.push(format!("path={}", serial.path.display()));
500                if let Some(mux) = &serial.mux {
501                    args.push(format!("mux={}", mux.to_arg()));
502                }
503                if let Some(logserial) = &serial.logfile {
504                    args.push(format!("logfile={}", logserial.display()));
505                }
506                if let Some(logappend) = &serial.logappend {
507                    args.push(format!("logappend={}", logappend.to_arg()));
508                }
509            }
510            CharDev::Pty(pty) => {
511                args.push("pty".to_string());
512                args.push(format!("id={}", pty.id));
513                if let Some(path) = &pty.path {
514                    args.push(format!("path={}", path.display()));
515                }
516                if let Some(mux) = &pty.mux {
517                    args.push(format!("mux={}", mux.to_arg()));
518                }
519                if let Some(logpty) = &pty.logfile {
520                    args.push(format!("logfile={}", logpty.display()));
521                }
522                if let Some(logappend) = &pty.logappend {
523                    args.push(format!("logappend={}", logappend.to_arg()));
524                }
525            }
526            CharDev::Stdio(stdio) => {
527                args.push("stdio".to_string());
528                args.push(format!("id={}", stdio.id));
529                if let Some(mux) = &stdio.mux {
530                    args.push(format!("mux={}", mux.to_arg()));
531                }
532                if let Some(signal) = &stdio.signal {
533                    args.push(format!("signal={}", signal.to_arg()));
534                }
535                if let Some(stdio) = &stdio.logfile {
536                    args.push(format!("logfile={}", stdio.display()));
537                }
538                if let Some(logappend) = &stdio.logappend {
539                    args.push(format!("logappend={}", logappend.to_arg()));
540                }
541            }
542            CharDev::Braille(braille) => {
543                args.push("braille".to_string());
544                args.push(format!("id={}", braille.id));
545                if let Some(mux) = &braille.mux {
546                    args.push(format!("mux={}", mux.to_arg()));
547                }
548                if let Some(braille) = &braille.logfile {
549                    args.push(format!("logfile={}", braille.display()));
550                }
551                if let Some(logappend) = &braille.logappend {
552                    args.push(format!("logappend={}", logappend.to_arg()));
553                }
554            }
555            CharDev::Serial(serial) => {
556                args.push("serial".to_string());
557                args.push(format!("id={}", serial.id));
558                args.push(format!("path={}", serial.path.display()));
559                if let Some(mux) = &serial.mux {
560                    args.push(format!("mux={}", mux.to_arg()));
561                }
562                if let Some(serial) = &serial.logfile {
563                    args.push(format!("logfile={}", serial.display()));
564                }
565                if let Some(logappend) = &serial.logappend {
566                    args.push(format!("logappend={}", logappend.to_arg()));
567                }
568            }
569            CharDev::Parallel(parallel) => {
570                args.push("parallel".to_string());
571                args.push(format!("id={}", parallel.id));
572                args.push(format!("path={}", parallel.path.display()));
573                if let Some(mux) = &parallel.mux {
574                    args.push(format!("mux={}", mux.to_arg()));
575                }
576                if let Some(parallel) = &parallel.logfile {
577                    args.push(format!("logfile={}", parallel.display()));
578                }
579                if let Some(logappend) = &parallel.logappend {
580                    args.push(format!("logappend={}", logappend.to_arg()));
581                }
582            }
583            CharDev::SpiceVmc(spice) => {
584                args.push("spicevmc".to_string());
585                args.push(format!("id={}", spice.id));
586                args.push(format!("name={}", spice.name));
587                if let Some(debug) = &spice.debug {
588                    args.push(format!("debug={}", debug));
589                }
590                if let Some(parallel) = &spice.logfile {
591                    args.push(format!("logfile={}", parallel.display()));
592                }
593                if let Some(logappend) = &spice.logappend {
594                    args.push(format!("logappend={}", logappend.to_arg()));
595                }
596            }
597            CharDev::SpicePort(spice) => {
598                args.push("spiceport".to_string());
599                args.push(format!("id={}", spice.id));
600                args.push(format!("name={}", spice.name));
601                if let Some(debug) = &spice.debug {
602                    args.push(format!("debug={}", debug));
603                }
604                if let Some(parallel) = &spice.logfile {
605                    args.push(format!("logfile={}", parallel.display()));
606                }
607                if let Some(logappend) = &spice.logappend {
608                    args.push(format!("logappend={}", logappend.to_arg()));
609                }
610            }
611        }
612
613        vec![args.join(DELIM_COMMA)]
614    }
615}
616
617impl FromStr for CharDev {
618    type Err = String;
619
620    fn from_str(s: &str) -> Result<Self, Self::Err> {
621        let mut parts = s.split(',');
622        let backend = parts.next().ok_or_else(|| "empty chardev argument".to_string())?;
623
624        match backend {
625            "null" => parse_null_chardev(parts.collect()),
626            "socket" => parse_socket_chardev(parts.collect()),
627            "udp" => parse_udp_chardev(parts.collect()),
628            "msmouse" => parse_msmouse_chardev(parts.collect()),
629            "hub" => parse_hub_chardev(parts.collect()),
630            "vc" => parse_vc_chardev(parts.collect()),
631            "ringbuf" => parse_ringbuf_chardev(parts.collect()),
632            "file" => parse_file_chardev(parts.collect()),
633            "pipe" => parse_pipe_chardev(parts.collect()),
634            "console" => parse_console_chardev(parts.collect()),
635            "serial" => parse_serial_chardev(parts.collect()),
636            "pty" => parse_pty_chardev(parts.collect()),
637            "stdio" => parse_stdio_chardev(parts.collect()),
638            "braille" => parse_braille_chardev(parts.collect()),
639            "parallel" => parse_parallel_chardev(parts.collect()),
640            "spicevmc" => parse_spice_chardev(parts.collect(), true),
641            "spiceport" => parse_spice_chardev(parts.collect(), false),
642            other => Err(format!("unsupported chardev backend: {other}")),
643        }
644    }
645}
646
647fn parse_null_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
648    let mut id = None;
649    let mut mux = None;
650    let mut logfile = None;
651    let mut logappend = None;
652
653    for part in parts {
654        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev null option: {part}"))?;
655        match key {
656            "id" => id = Some(value.to_string()),
657            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
658            "logfile" => logfile = Some(PathBuf::from(value)),
659            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
660            other => return Err(format!("unsupported chardev null option: {other}")),
661        }
662    }
663
664    Ok(CharDev::Null(CharNull {
665        id: id.ok_or_else(|| "null chardev requires id=".to_string())?,
666        mux,
667        logfile,
668        logappend,
669    }))
670}
671
672fn parse_socket_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
673    let mut id = None;
674    let mut host = None;
675    let mut port = None;
676    let mut path = None;
677    let mut to = None;
678    let mut ipv4 = None;
679    let mut ipv6 = None;
680    let mut nodelay = None;
681    let mut server = None;
682    let mut wait = None;
683    let mut telnet = None;
684    let mut websocket = None;
685    let mut reconnect_ms = None;
686    let mut mux = None;
687    let mut logfile = None;
688    let mut logappend = None;
689    let mut tls_creds = None;
690    let mut tls_authz = None;
691    let mut abstract_opt = None;
692    let mut tight = None;
693
694    for part in parts {
695        let Some((key, value)) = part.split_once('=') else {
696            match part {
697                "server" => {
698                    server = Some(OnOff::On);
699                    continue;
700                }
701                "nowait" => {
702                    wait = Some(OnOff::Off);
703                    continue;
704                }
705                "wait" => {
706                    wait = Some(OnOff::On);
707                    continue;
708                }
709                _ => return Err(format!("invalid chardev socket option: {part}")),
710            }
711        };
712        match key {
713            "id" => id = Some(value.to_string()),
714            "host" => host = Some(value.to_string()),
715            "port" => port = Some(value.parse::<u16>().map_err(|e| e.to_string())?),
716            "path" => path = Some(PathBuf::from(value)),
717            "to" => to = Some(value.parse::<u16>().map_err(|e| e.to_string())?),
718            "ipv4" => ipv4 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv4 value: {value}"))?),
719            "ipv6" => ipv6 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv6 value: {value}"))?),
720            "nodelay" => nodelay = Some(value.parse::<OnOff>().map_err(|_| format!("invalid nodelay value: {value}"))?),
721            "server" => server = Some(value.parse::<OnOff>().map_err(|_| format!("invalid server value: {value}"))?),
722            "wait" => wait = Some(value.parse::<OnOff>().map_err(|_| format!("invalid wait value: {value}"))?),
723            "telnet" => telnet = Some(value.parse::<OnOff>().map_err(|_| format!("invalid telnet value: {value}"))?),
724            "websocket" => websocket = Some(value.parse::<OnOff>().map_err(|_| format!("invalid websocket value: {value}"))?),
725            "reconnect-ms" => reconnect_ms = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
726            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
727            "logfile" => logfile = Some(PathBuf::from(value)),
728            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
729            "tls-creds" => tls_creds = Some(value.to_string()),
730            "tls-authz" => tls_authz = Some(value.to_string()),
731            "abstract" => abstract_opt = Some(value.parse::<OnOff>().map_err(|_| format!("invalid abstract value: {value}"))?),
732            "tight" => tight = Some(value.parse::<OnOff>().map_err(|_| format!("invalid tight value: {value}"))?),
733            other => return Err(format!("unsupported chardev socket option: {other}")),
734        }
735    }
736
737    let id = id.ok_or_else(|| "socket chardev requires id=".to_string())?;
738    if let Some(path) = path {
739        return Ok(CharDev::Socket(CharSocket::Uds(CharSocketUds {
740            id,
741            path,
742            server,
743            wait,
744            telnet,
745            websocket,
746            reconnect_ms,
747            mux,
748            logfile,
749            logappend,
750            abstract_opt,
751            tight,
752        })));
753    }
754
755    Ok(CharDev::Socket(CharSocket::Tcp(CharSocketTcp {
756        id,
757        host,
758        port: port.ok_or_else(|| "tcp socket chardev requires port=".to_string())?,
759        to,
760        ipv4,
761        ipv6,
762        nodelay,
763        server,
764        wait,
765        telnet,
766        websocket,
767        reconnect_ms,
768        mux,
769        logfile,
770        logappend,
771        tls_creds,
772        tls_authz,
773    })))
774}
775
776fn parse_udp_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
777    let mut id = None;
778    let mut host = None;
779    let mut port = None;
780    let mut localaddr = None;
781    let mut localport = None;
782    let mut ipv4 = None;
783    let mut ipv6 = None;
784    let mut mux = None;
785    let mut logfile = None;
786    let mut logappend = None;
787
788    for part in parts {
789        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev udp option: {part}"))?;
790        match key {
791            "id" => id = Some(value.to_string()),
792            "host" => host = Some(value.to_string()),
793            "port" => port = Some(value.parse::<u16>().map_err(|e| e.to_string())?),
794            "localaddr" => localaddr = Some(value.to_string()),
795            "localport" => localport = Some(value.parse::<u16>().map_err(|e| e.to_string())?),
796            "ipv4" => ipv4 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv4 value: {value}"))?),
797            "ipv6" => ipv6 = Some(value.parse::<OnOff>().map_err(|_| format!("invalid ipv6 value: {value}"))?),
798            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
799            "logfile" => logfile = Some(PathBuf::from(value)),
800            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
801            other => return Err(format!("unsupported chardev udp option: {other}")),
802        }
803    }
804
805    Ok(CharDev::Udp(CharUdp {
806        id: id.ok_or_else(|| "udp chardev requires id=".to_string())?,
807        host,
808        port: port.ok_or_else(|| "udp chardev requires port=".to_string())?,
809        localaddr,
810        localport,
811        ipv4,
812        ipv6,
813        mux,
814        logfile,
815        logappend,
816    }))
817}
818
819fn parse_msmouse_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
820    let mut id = None;
821    let mut mux = None;
822    let mut logfile = None;
823    let mut logappend = None;
824
825    for part in parts {
826        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev msmouse option: {part}"))?;
827        match key {
828            "id" => id = Some(value.to_string()),
829            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
830            "logfile" => logfile = Some(PathBuf::from(value)),
831            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
832            other => return Err(format!("unsupported chardev msmouse option: {other}")),
833        }
834    }
835
836    Ok(CharDev::MsMouse(CharMsMouse {
837        id: id.ok_or_else(|| "msmouse chardev requires id=".to_string())?,
838        mux,
839        logfile,
840        logappend,
841    }))
842}
843
844fn parse_hub_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
845    let mut id = None;
846    let mut chardevs = Vec::new();
847
848    for part in parts {
849        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev hub option: {part}"))?;
850        if key == "id" {
851            id = Some(value.to_string());
852        } else if let Some(index) = key.strip_prefix("chardevs.") {
853            chardevs.push((index.parse::<usize>().map_err(|e| e.to_string())?, value.to_string()));
854        } else {
855            return Err(format!("unsupported chardev hub option: {key}"));
856        }
857    }
858
859    chardevs.sort_by_key(|(n, _)| *n);
860    Ok(CharDev::Hub(CharHub {
861        id: id.ok_or_else(|| "hub chardev requires id=".to_string())?,
862        chardevs: (!chardevs.is_empty()).then_some(chardevs),
863    }))
864}
865
866fn parse_vc_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
867    let mut id = None;
868    let mut width = None;
869    let mut height = None;
870    let mut cols = None;
871    let mut rows = None;
872    let mut mux = None;
873    let mut logfile = None;
874    let mut logappend = None;
875
876    for part in parts {
877        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev vc option: {part}"))?;
878        match key {
879            "id" => id = Some(value.to_string()),
880            "width" => width = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
881            "height" => height = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
882            "cols" => cols = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
883            "rows" => rows = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
884            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
885            "logfile" => logfile = Some(PathBuf::from(value)),
886            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
887            other => return Err(format!("unsupported chardev vc option: {other}")),
888        }
889    }
890
891    Ok(CharDev::Vc(CharVc {
892        id: id.ok_or_else(|| "vc chardev requires id=".to_string())?,
893        width,
894        height,
895        cols,
896        rows,
897        mux,
898        logfile,
899        logappend,
900    }))
901}
902
903fn parse_ringbuf_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
904    let mut id = None;
905    let mut size = None;
906    let mut logfile = None;
907    let mut logappend = None;
908
909    for part in parts {
910        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev ringbuf option: {part}"))?;
911        match key {
912            "id" => id = Some(value.to_string()),
913            "size" => size = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
914            "logfile" => logfile = Some(PathBuf::from(value)),
915            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
916            other => return Err(format!("unsupported chardev ringbuf option: {other}")),
917        }
918    }
919
920    Ok(CharDev::RingBuf(CharRingBuf {
921        id: id.ok_or_else(|| "ringbuf chardev requires id=".to_string())?,
922        size,
923        logfile,
924        logappend,
925    }))
926}
927
928fn parse_file_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
929    let mut id = None;
930    let mut path = None;
931    let mut input_path = None;
932    let mut mux = None;
933    let mut logfile = None;
934    let mut logappend = None;
935
936    for part in parts {
937        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev file option: {part}"))?;
938        match key {
939            "id" => id = Some(value.to_string()),
940            "path" => path = Some(PathBuf::from(value)),
941            "input-path" => input_path = Some(PathBuf::from(value)),
942            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
943            "logfile" => logfile = Some(PathBuf::from(value)),
944            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
945            other => return Err(format!("unsupported chardev file option: {other}")),
946        }
947    }
948
949    Ok(CharDev::File(CharFile {
950        id: id.ok_or_else(|| "file chardev requires id=".to_string())?,
951        path: path.ok_or_else(|| "file chardev requires path=".to_string())?,
952        input_path,
953        mux,
954        logfile,
955        logappend,
956    }))
957}
958
959fn parse_pipe_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
960    let mut id = None;
961    let mut path = None;
962    let mut mux = None;
963    let mut logfile = None;
964    let mut logappend = None;
965
966    for part in parts {
967        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev pipe option: {part}"))?;
968        match key {
969            "id" => id = Some(value.to_string()),
970            "path" => path = Some(PathBuf::from(value)),
971            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
972            "logfile" => logfile = Some(PathBuf::from(value)),
973            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
974            other => return Err(format!("unsupported chardev pipe option: {other}")),
975        }
976    }
977
978    Ok(CharDev::Pipe(CharPipe {
979        id: id.ok_or_else(|| "pipe chardev requires id=".to_string())?,
980        path: path.ok_or_else(|| "pipe chardev requires path=".to_string())?,
981        mux,
982        logfile,
983        logappend,
984    }))
985}
986
987fn parse_console_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
988    let mut id = None;
989    let mut mux = None;
990    let mut logfile = None;
991    let mut logappend = None;
992
993    for part in parts {
994        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev console option: {part}"))?;
995        match key {
996            "id" => id = Some(value.to_string()),
997            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
998            "logfile" => logfile = Some(PathBuf::from(value)),
999            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1000            other => return Err(format!("unsupported chardev console option: {other}")),
1001        }
1002    }
1003
1004    Ok(CharDev::Win32Console(CharWin32Console {
1005        id: id.ok_or_else(|| "console chardev requires id=".to_string())?,
1006        mux,
1007        logfile,
1008        logappend,
1009    }))
1010}
1011
1012fn parse_serial_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
1013    let mut id = None;
1014    let mut path = None;
1015    let mut mux = None;
1016    let mut logfile = None;
1017    let mut logappend = None;
1018
1019    for part in parts {
1020        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev serial option: {part}"))?;
1021        match key {
1022            "id" => id = Some(value.to_string()),
1023            "path" => path = Some(PathBuf::from(value)),
1024            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
1025            "logfile" => logfile = Some(PathBuf::from(value)),
1026            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1027            other => return Err(format!("unsupported chardev serial option: {other}")),
1028        }
1029    }
1030
1031    Ok(CharDev::Serial(CharSerial {
1032        id: id.ok_or_else(|| "serial chardev requires id=".to_string())?,
1033        path: path.ok_or_else(|| "serial chardev requires path=".to_string())?,
1034        mux,
1035        logfile,
1036        logappend,
1037    }))
1038}
1039
1040fn parse_pty_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
1041    let mut id = None;
1042    let mut path = None;
1043    let mut mux = None;
1044    let mut logfile = None;
1045    let mut logappend = None;
1046
1047    for part in parts {
1048        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev pty option: {part}"))?;
1049        match key {
1050            "id" => id = Some(value.to_string()),
1051            "path" => path = Some(PathBuf::from(value)),
1052            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
1053            "logfile" => logfile = Some(PathBuf::from(value)),
1054            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1055            other => return Err(format!("unsupported chardev pty option: {other}")),
1056        }
1057    }
1058
1059    Ok(CharDev::Pty(CharPty {
1060        id: id.ok_or_else(|| "pty chardev requires id=".to_string())?,
1061        path,
1062        mux,
1063        logfile,
1064        logappend,
1065    }))
1066}
1067
1068fn parse_stdio_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
1069    let mut id = None;
1070    let mut mux = None;
1071    let mut signal = None;
1072    let mut logfile = None;
1073    let mut logappend = None;
1074
1075    for part in parts {
1076        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev stdio option: {part}"))?;
1077        match key {
1078            "id" => id = Some(value.to_string()),
1079            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
1080            "signal" => signal = Some(value.parse::<OnOff>().map_err(|_| format!("invalid signal value: {value}"))?),
1081            "logfile" => logfile = Some(PathBuf::from(value)),
1082            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1083            other => return Err(format!("unsupported chardev stdio option: {other}")),
1084        }
1085    }
1086
1087    Ok(CharDev::Stdio(CharStdio {
1088        id: id.ok_or_else(|| "stdio chardev requires id=".to_string())?,
1089        mux,
1090        signal,
1091        logfile,
1092        logappend,
1093    }))
1094}
1095
1096fn parse_braille_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
1097    let mut id = None;
1098    let mut mux = None;
1099    let mut logfile = None;
1100    let mut logappend = None;
1101
1102    for part in parts {
1103        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev braille option: {part}"))?;
1104        match key {
1105            "id" => id = Some(value.to_string()),
1106            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
1107            "logfile" => logfile = Some(PathBuf::from(value)),
1108            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1109            other => return Err(format!("unsupported chardev braille option: {other}")),
1110        }
1111    }
1112
1113    Ok(CharDev::Braille(CharBraille {
1114        id: id.ok_or_else(|| "braille chardev requires id=".to_string())?,
1115        mux,
1116        logfile,
1117        logappend,
1118    }))
1119}
1120
1121fn parse_parallel_chardev(parts: Vec<&str>) -> Result<CharDev, String> {
1122    let mut id = None;
1123    let mut path = None;
1124    let mut mux = None;
1125    let mut logfile = None;
1126    let mut logappend = None;
1127
1128    for part in parts {
1129        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev parallel option: {part}"))?;
1130        match key {
1131            "id" => id = Some(value.to_string()),
1132            "path" => path = Some(PathBuf::from(value)),
1133            "mux" => mux = Some(value.parse::<OnOff>().map_err(|_| format!("invalid mux value: {value}"))?),
1134            "logfile" => logfile = Some(PathBuf::from(value)),
1135            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1136            other => return Err(format!("unsupported chardev parallel option: {other}")),
1137        }
1138    }
1139
1140    Ok(CharDev::Parallel(CharParallel {
1141        id: id.ok_or_else(|| "parallel chardev requires id=".to_string())?,
1142        path: path.ok_or_else(|| "parallel chardev requires path=".to_string())?,
1143        mux,
1144        logfile,
1145        logappend,
1146    }))
1147}
1148
1149fn parse_spice_chardev(parts: Vec<&str>, is_vmc: bool) -> Result<CharDev, String> {
1150    let mut id = None;
1151    let mut name = None;
1152    let mut debug = None;
1153    let mut logfile = None;
1154    let mut logappend = None;
1155
1156    for part in parts {
1157        let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid chardev spice option: {part}"))?;
1158        match key {
1159            "id" => id = Some(value.to_string()),
1160            "name" => name = Some(value.to_string()),
1161            "debug" => debug = Some(value.to_string()),
1162            "logfile" => logfile = Some(PathBuf::from(value)),
1163            "logappend" => logappend = Some(value.parse::<OnOff>().map_err(|_| format!("invalid logappend value: {value}"))?),
1164            other => return Err(format!("unsupported chardev spice option: {other}")),
1165        }
1166    }
1167
1168    let spice = CharSpice {
1169        id: id.ok_or_else(|| "spice chardev requires id=".to_string())?,
1170        name: name.ok_or_else(|| "spice chardev requires name=".to_string())?,
1171        debug,
1172        logfile,
1173        logappend,
1174    };
1175
1176    Ok(if is_vmc { CharDev::SpiceVmc(spice) } else { CharDev::SpicePort(spice) })
1177}