1use std::io;
2
3pub fn baud_to_num(speed: libc::speed_t) -> u32 {
5 match speed {
6 libc::B0 => 0,
7 libc::B50 => 50,
8 libc::B75 => 75,
9 libc::B110 => 110,
10 libc::B134 => 134,
11 libc::B150 => 150,
12 libc::B200 => 200,
13 libc::B300 => 300,
14 libc::B600 => 600,
15 libc::B1200 => 1200,
16 libc::B1800 => 1800,
17 libc::B2400 => 2400,
18 libc::B4800 => 4800,
19 libc::B9600 => 9600,
20 libc::B19200 => 19200,
21 libc::B38400 => 38400,
22 libc::B57600 => 57600,
23 libc::B115200 => 115200,
24 libc::B230400 => 230400,
25 _ => 0,
26 }
27}
28
29pub fn num_to_baud(num: u32) -> Option<libc::speed_t> {
31 match num {
32 0 => Some(libc::B0),
33 50 => Some(libc::B50),
34 75 => Some(libc::B75),
35 110 => Some(libc::B110),
36 134 => Some(libc::B134),
37 150 => Some(libc::B150),
38 200 => Some(libc::B200),
39 300 => Some(libc::B300),
40 600 => Some(libc::B600),
41 1200 => Some(libc::B1200),
42 1800 => Some(libc::B1800),
43 2400 => Some(libc::B2400),
44 4800 => Some(libc::B4800),
45 9600 => Some(libc::B9600),
46 19200 => Some(libc::B19200),
47 38400 => Some(libc::B38400),
48 57600 => Some(libc::B57600),
49 115200 => Some(libc::B115200),
50 230400 => Some(libc::B230400),
51 _ => None,
52 }
53}
54
55pub fn get_termios(fd: i32) -> io::Result<libc::termios> {
57 let mut termios: libc::termios = unsafe { std::mem::zeroed() };
58 if unsafe { libc::tcgetattr(fd, &mut termios) } != 0 {
59 return Err(io::Error::last_os_error());
60 }
61 Ok(termios)
62}
63
64pub fn set_termios(fd: i32, termios: &libc::termios) -> io::Result<()> {
66 if unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, termios) } != 0 {
67 return Err(io::Error::last_os_error());
68 }
69 Ok(())
70}
71
72pub fn get_winsize(fd: i32) -> io::Result<libc::winsize> {
74 let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
75 if unsafe { libc::ioctl(fd, libc::TIOCGWINSZ, &mut ws) } != 0 {
76 return Err(io::Error::last_os_error());
77 }
78 Ok(ws)
79}
80
81pub fn print_size(fd: i32) -> io::Result<()> {
83 let ws = get_winsize(fd)?;
84 println!("{} {}", ws.ws_row, ws.ws_col);
85 Ok(())
86}
87
88pub fn print_speed(termios: &libc::termios) {
90 let ispeed = unsafe { libc::cfgetispeed(termios) };
91 let ospeed = unsafe { libc::cfgetospeed(termios) };
92 if ispeed == ospeed {
93 println!("{}", baud_to_num(ospeed));
94 } else {
95 println!("{} {}", baud_to_num(ispeed), baud_to_num(ospeed));
96 }
97}
98
99pub fn format_cc(c: libc::cc_t) -> String {
101 if c == 0 {
102 "<undef>".to_string()
103 } else if c == 0x7f {
104 "^?".to_string()
105 } else if c < 0x20 {
106 format!("^{}", (c + 0x40) as char)
107 } else {
108 format!("{}", c as char)
109 }
110}
111
112const SPECIAL_CHARS: &[(&str, usize)] = &[
114 ("intr", libc::VINTR as usize),
115 ("quit", libc::VQUIT as usize),
116 ("erase", libc::VERASE as usize),
117 ("kill", libc::VKILL as usize),
118 ("eof", libc::VEOF as usize),
119 ("eol", libc::VEOL as usize),
120 ("eol2", libc::VEOL2 as usize),
121 ("start", libc::VSTART as usize),
122 ("stop", libc::VSTOP as usize),
123 ("susp", libc::VSUSP as usize),
124 ("rprnt", libc::VREPRINT as usize),
125 ("werase", libc::VWERASE as usize),
126 ("lnext", libc::VLNEXT as usize),
127 ("discard", libc::VDISCARD as usize),
128 ("min", libc::VMIN as usize),
129 ("time", libc::VTIME as usize),
130];
131
132#[cfg(target_os = "linux")]
134const SPECIAL_CHARS_LINUX: &[(&str, usize)] = &[("swtch", libc::VSWTC as usize)];
135
136#[cfg(not(target_os = "linux"))]
137const SPECIAL_CHARS_LINUX: &[(&str, usize)] = &[];
138
139const INPUT_FLAGS: &[(&str, libc::tcflag_t)] = &[
141 ("ignbrk", libc::IGNBRK),
142 ("brkint", libc::BRKINT),
143 ("ignpar", libc::IGNPAR),
144 ("parmrk", libc::PARMRK),
145 ("inpck", libc::INPCK),
146 ("istrip", libc::ISTRIP),
147 ("inlcr", libc::INLCR),
148 ("igncr", libc::IGNCR),
149 ("icrnl", libc::ICRNL),
150 ("ixon", libc::IXON),
151 ("ixany", libc::IXANY),
152 ("ixoff", libc::IXOFF),
153 ("imaxbel", libc::IMAXBEL),
154];
155
156#[cfg(target_os = "linux")]
158const INPUT_FLAGS_LINUX: &[(&str, libc::tcflag_t)] =
159 &[("iuclc", libc::IUCLC), ("iutf8", libc::IUTF8)];
160
161#[cfg(not(target_os = "linux"))]
162const INPUT_FLAGS_LINUX: &[(&str, libc::tcflag_t)] = &[];
163
164const OUTPUT_FLAGS: &[(&str, libc::tcflag_t)] = &[
166 ("opost", libc::OPOST),
167 ("onlcr", libc::ONLCR),
168 ("ocrnl", libc::OCRNL),
169 ("onocr", libc::ONOCR),
170 ("onlret", libc::ONLRET),
171 ("ofill", libc::OFILL),
172 ("ofdel", libc::OFDEL),
173];
174
175#[cfg(target_os = "linux")]
177const OUTPUT_FLAGS_LINUX: &[(&str, libc::tcflag_t)] = &[("olcuc", libc::OLCUC)];
178
179#[cfg(not(target_os = "linux"))]
180const OUTPUT_FLAGS_LINUX: &[(&str, libc::tcflag_t)] = &[];
181
182const CONTROL_FLAGS: &[(&str, libc::tcflag_t)] = &[
184 ("cread", libc::CREAD),
185 ("clocal", libc::CLOCAL),
186 ("hupcl", libc::HUPCL),
187 ("cstopb", libc::CSTOPB),
188 ("parenb", libc::PARENB),
189 ("parodd", libc::PARODD),
190];
191
192const LOCAL_FLAGS: &[(&str, libc::tcflag_t)] = &[
194 ("isig", libc::ISIG),
195 ("icanon", libc::ICANON),
196 ("iexten", libc::IEXTEN),
197 ("echo", libc::ECHO),
198 ("echoe", libc::ECHOE),
199 ("echok", libc::ECHOK),
200 ("echonl", libc::ECHONL),
201 ("echoctl", libc::ECHOCTL),
202 ("echoprt", libc::ECHOPRT),
203 ("echoke", libc::ECHOKE),
204 ("noflsh", libc::NOFLSH),
205 ("tostop", libc::TOSTOP),
206];
207
208#[cfg(target_os = "linux")]
210const LOCAL_FLAGS_LINUX: &[(&str, libc::tcflag_t)] = &[("xcase", libc::XCASE)];
211
212#[cfg(not(target_os = "linux"))]
213const LOCAL_FLAGS_LINUX: &[(&str, libc::tcflag_t)] = &[];
214
215fn csize_str(cflag: libc::tcflag_t) -> &'static str {
217 match cflag & libc::CSIZE {
218 libc::CS5 => "cs5",
219 libc::CS6 => "cs6",
220 libc::CS7 => "cs7",
221 libc::CS8 => "cs8",
222 _ => "cs8",
223 }
224}
225
226fn print_flags(
228 parts: &mut Vec<String>,
229 flags: libc::tcflag_t,
230 entries: &[(&str, libc::tcflag_t)],
231 extra: &[(&str, libc::tcflag_t)],
232) {
233 for &(name, flag) in entries.iter().chain(extra.iter()) {
234 if flags & flag != 0 {
235 parts.push(name.to_string());
236 } else {
237 parts.push(format!("-{}", name));
238 }
239 }
240}
241
242pub fn print_all(termios: &libc::termios, fd: i32) {
244 let ispeed = unsafe { libc::cfgetispeed(termios) };
245 let ospeed = unsafe { libc::cfgetospeed(termios) };
246
247 let speed_str = if ispeed == ospeed {
249 format!("speed {} baud", baud_to_num(ospeed))
250 } else {
251 format!(
252 "speed {} baud; ispeed {} baud; ospeed {} baud",
253 baud_to_num(ospeed),
254 baud_to_num(ispeed),
255 baud_to_num(ospeed)
256 )
257 };
258 if let Ok(ws) = get_winsize(fd) {
259 println!("{}; rows {}; columns {};", speed_str, ws.ws_row, ws.ws_col);
260 } else {
261 println!("{};", speed_str);
262 }
263
264 let mut cc_parts: Vec<String> = Vec::new();
266 for &(name, idx) in SPECIAL_CHARS.iter().chain(SPECIAL_CHARS_LINUX.iter()) {
267 cc_parts.push(format!("{} = {}", name, format_cc(termios.c_cc[idx])));
268 }
269 println!("{};", cc_parts.join("; "));
270
271 let mut parts: Vec<String> = Vec::new();
273 print_flags(&mut parts, termios.c_iflag, INPUT_FLAGS, INPUT_FLAGS_LINUX);
274 println!("{}", parts.join(" "));
275
276 parts.clear();
278 print_flags(
279 &mut parts,
280 termios.c_oflag,
281 OUTPUT_FLAGS,
282 OUTPUT_FLAGS_LINUX,
283 );
284 println!("{}", parts.join(" "));
285
286 parts.clear();
288 parts.push(csize_str(termios.c_cflag).to_string());
289 print_flags(&mut parts, termios.c_cflag, CONTROL_FLAGS, &[]);
290 println!("{}", parts.join(" "));
291
292 parts.clear();
294 print_flags(&mut parts, termios.c_lflag, LOCAL_FLAGS, LOCAL_FLAGS_LINUX);
295 println!("{}", parts.join(" "));
296}
297
298pub fn parse_control_char(s: &str) -> Option<libc::cc_t> {
300 if s == "^-" || s == "undef" {
301 Some(0)
302 } else if s == "^?" {
303 Some(0x7f)
304 } else if s.len() == 2 && s.starts_with('^') {
305 let ch = s.as_bytes()[1];
306 if ch >= b'@' && ch <= b'_' {
307 Some(ch - b'@')
308 } else if ch >= b'a' && ch <= b'z' {
309 Some(ch - b'a' + 1)
310 } else {
311 None
312 }
313 } else if s.len() == 1 {
314 Some(s.as_bytes()[0])
315 } else {
316 s.parse::<u8>().ok()
318 }
319}
320
321pub fn set_sane(termios: &mut libc::termios) {
323 termios.c_iflag = libc::BRKINT | libc::ICRNL | libc::IMAXBEL | libc::IXON;
325 #[cfg(target_os = "linux")]
326 {
327 termios.c_iflag |= libc::IUTF8;
328 }
329
330 termios.c_oflag = libc::OPOST | libc::ONLCR;
332
333 #[cfg(target_os = "linux")]
335 {
336 termios.c_cflag = (termios.c_cflag & (libc::CBAUD | libc::CBAUDEX))
337 | libc::CS8
338 | libc::CREAD
339 | libc::HUPCL;
340 }
341 #[cfg(not(target_os = "linux"))]
342 {
343 let ispeed = unsafe { libc::cfgetispeed(termios) };
346 let ospeed = unsafe { libc::cfgetospeed(termios) };
347 termios.c_cflag = libc::CS8 | libc::CREAD | libc::HUPCL;
348 unsafe {
349 libc::cfsetispeed(termios, ispeed);
350 libc::cfsetospeed(termios, ospeed);
351 }
352 }
353
354 termios.c_lflag = libc::ISIG
356 | libc::ICANON
357 | libc::IEXTEN
358 | libc::ECHO
359 | libc::ECHOE
360 | libc::ECHOK
361 | libc::ECHOCTL
362 | libc::ECHOKE;
363
364 termios.c_cc[libc::VINTR] = 0x03; termios.c_cc[libc::VQUIT] = 0x1c; termios.c_cc[libc::VERASE] = 0x7f; termios.c_cc[libc::VKILL] = 0x15; termios.c_cc[libc::VEOF] = 0x04; termios.c_cc[libc::VSTART] = 0x11; termios.c_cc[libc::VSTOP] = 0x13; termios.c_cc[libc::VSUSP] = 0x1a; termios.c_cc[libc::VREPRINT] = 0x12; termios.c_cc[libc::VWERASE] = 0x17; termios.c_cc[libc::VLNEXT] = 0x16; termios.c_cc[libc::VDISCARD] = 0x0f; termios.c_cc[libc::VMIN] = 1;
378 termios.c_cc[libc::VTIME] = 0;
379}
380
381pub fn set_raw(termios: &mut libc::termios) {
383 termios.c_iflag &= !(libc::IGNBRK
385 | libc::BRKINT
386 | libc::PARMRK
387 | libc::ISTRIP
388 | libc::INLCR
389 | libc::IGNCR
390 | libc::ICRNL
391 | libc::IXON);
392 termios.c_oflag &= !libc::OPOST;
393 termios.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
394 termios.c_cflag &= !(libc::CSIZE | libc::PARENB);
395 termios.c_cflag |= libc::CS8;
396 termios.c_cc[libc::VMIN] = 1;
397 termios.c_cc[libc::VTIME] = 0;
398}
399
400pub fn set_cooked(termios: &mut libc::termios) {
402 termios.c_iflag |= libc::BRKINT | libc::IGNPAR | libc::ICRNL | libc::IXON;
403 termios.c_oflag |= libc::OPOST;
404 termios.c_lflag |= libc::ISIG | libc::ICANON | libc::ECHO;
405}
406
407pub fn open_device(path: &str) -> io::Result<i32> {
409 use std::ffi::CString;
410 let cpath = CString::new(path)
411 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid device path"))?;
412 let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY | libc::O_NONBLOCK) };
413 if fd < 0 {
414 return Err(io::Error::last_os_error());
415 }
416 Ok(fd)
417}
418
419pub fn find_special_char(name: &str) -> Option<usize> {
421 for &(n, idx) in SPECIAL_CHARS.iter().chain(SPECIAL_CHARS_LINUX.iter()) {
422 if n == name {
423 return Some(idx);
424 }
425 }
426 None
427}
428
429pub fn apply_flag(termios: &mut libc::termios, name: &str) -> bool {
431 let (negate, flag_name) = if let Some(stripped) = name.strip_prefix('-') {
432 (true, stripped)
433 } else {
434 (false, name)
435 };
436
437 for &(n, flag) in INPUT_FLAGS.iter().chain(INPUT_FLAGS_LINUX.iter()) {
439 if n == flag_name {
440 if negate {
441 termios.c_iflag &= !flag;
442 } else {
443 termios.c_iflag |= flag;
444 }
445 return true;
446 }
447 }
448
449 for &(n, flag) in OUTPUT_FLAGS.iter().chain(OUTPUT_FLAGS_LINUX.iter()) {
451 if n == flag_name {
452 if negate {
453 termios.c_oflag &= !flag;
454 } else {
455 termios.c_oflag |= flag;
456 }
457 return true;
458 }
459 }
460
461 for &(n, flag) in CONTROL_FLAGS {
463 if n == flag_name {
464 if negate {
465 termios.c_cflag &= !flag;
466 } else {
467 termios.c_cflag |= flag;
468 }
469 return true;
470 }
471 }
472
473 for &(n, flag) in LOCAL_FLAGS.iter().chain(LOCAL_FLAGS_LINUX.iter()) {
475 if n == flag_name {
476 if negate {
477 termios.c_lflag &= !flag;
478 } else {
479 termios.c_lflag |= flag;
480 }
481 return true;
482 }
483 }
484
485 match flag_name {
487 "cs5" => {
488 termios.c_cflag = (termios.c_cflag & !libc::CSIZE) | libc::CS5;
489 return true;
490 }
491 "cs6" => {
492 termios.c_cflag = (termios.c_cflag & !libc::CSIZE) | libc::CS6;
493 return true;
494 }
495 "cs7" => {
496 termios.c_cflag = (termios.c_cflag & !libc::CSIZE) | libc::CS7;
497 return true;
498 }
499 "cs8" => {
500 termios.c_cflag = (termios.c_cflag & !libc::CSIZE) | libc::CS8;
501 return true;
502 }
503 _ => {}
504 }
505
506 false
507}
508
509pub enum SttyAction {
511 PrintAll,
512 PrintSize,
513 PrintSpeed,
514 ApplySettings,
515}
516
517pub struct SttyConfig {
519 pub action: SttyAction,
520 pub device: Option<String>,
521 pub settings: Vec<String>,
522}
523
524pub fn parse_args(args: &[String]) -> Result<SttyConfig, String> {
526 let mut action = SttyAction::ApplySettings;
527 let mut device: Option<String> = None;
528 let mut settings: Vec<String> = Vec::new();
529 let mut i = 0;
530 let mut has_explicit_action = false;
531
532 while i < args.len() {
533 match args[i].as_str() {
534 "-a" | "--all" => {
535 action = SttyAction::PrintAll;
536 has_explicit_action = true;
537 }
538 "-F" | "--file" => {
539 i += 1;
540 if i >= args.len() {
541 return Err("option requires an argument -- 'F'".to_string());
542 }
543 device = Some(args[i].clone());
544 }
545 s if s.starts_with("--file=") => {
546 device = Some(s["--file=".len()..].to_string());
547 }
548 s if s.starts_with("-F") && s.len() > 2 => {
549 device = Some(s[2..].to_string());
550 }
551 "size" => {
552 action = SttyAction::PrintSize;
553 has_explicit_action = true;
554 }
555 "speed" => {
556 action = SttyAction::PrintSpeed;
557 has_explicit_action = true;
558 }
559 _ => {
560 settings.push(args[i].clone());
561 }
562 }
563 i += 1;
564 }
565
566 if !has_explicit_action && settings.is_empty() {
567 action = SttyAction::PrintAll;
568 }
569
570 Ok(SttyConfig {
571 action,
572 device,
573 settings,
574 })
575}
576
577pub fn apply_settings(termios: &mut libc::termios, settings: &[String]) -> Result<bool, String> {
580 let mut changed = false;
581 let mut i = 0;
582
583 while i < settings.len() {
584 let arg = &settings[i];
585
586 match arg.as_str() {
587 "sane" => {
588 set_sane(termios);
589 changed = true;
590 }
591 "raw" => {
592 set_raw(termios);
593 changed = true;
594 }
595 "-raw" | "cooked" => {
596 set_cooked(termios);
597 changed = true;
598 }
599 "ispeed" => {
600 i += 1;
601 if i >= settings.len() {
602 return Err("missing argument to 'ispeed'".to_string());
603 }
604 let n: u32 = settings[i]
605 .parse()
606 .map_err(|_| format!("invalid integer argument: '{}'", settings[i]))?;
607 let baud = num_to_baud(n).ok_or_else(|| format!("invalid speed: '{}'", n))?;
608 unsafe {
609 libc::cfsetispeed(termios, baud);
610 }
611 changed = true;
612 }
613 "ospeed" => {
614 i += 1;
615 if i >= settings.len() {
616 return Err("missing argument to 'ospeed'".to_string());
617 }
618 let n: u32 = settings[i]
619 .parse()
620 .map_err(|_| format!("invalid integer argument: '{}'", settings[i]))?;
621 let baud = num_to_baud(n).ok_or_else(|| format!("invalid speed: '{}'", n))?;
622 unsafe {
623 libc::cfsetospeed(termios, baud);
624 }
625 changed = true;
626 }
627 _ => {
628 if let Ok(n) = arg.parse::<u32>() {
630 if let Some(baud) = num_to_baud(n) {
631 unsafe {
632 libc::cfsetispeed(termios, baud);
633 libc::cfsetospeed(termios, baud);
634 }
635 changed = true;
636 i += 1;
637 continue;
638 }
639 }
640
641 if let Some(idx) = find_special_char(arg) {
643 i += 1;
644 if i >= settings.len() {
645 return Err(format!("missing argument to '{}'", arg));
646 }
647 let cc = parse_control_char(&settings[i])
648 .ok_or_else(|| format!("invalid integer argument: '{}'", settings[i]))?;
649 termios.c_cc[idx] = cc;
650 changed = true;
651 i += 1;
652 continue;
653 }
654
655 if !apply_flag(termios, arg) {
657 return Err(format!("invalid argument '{}'", arg));
658 }
659 changed = true;
660 }
661 }
662
663 i += 1;
664 }
665
666 Ok(changed)
667}