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