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