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#[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 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#[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 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) = ¶llel.mux {
651 args.push(format!("mux={}", mux.to_arg()));
652 }
653 if let Some(parallel) = ¶llel.logfile {
654 args.push(format!("logfile={}", parallel.display()));
655 }
656 if let Some(logappend) = ¶llel.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}