1use std::mem::MaybeUninit;
2use std::os::unix::prelude::*;
3use std::path::Path;
4use std::time::Duration;
5use std::{io, mem};
6
7use nix::fcntl::{fcntl, OFlag};
8use nix::{libc, unistd};
9
10use crate::posix::ioctl::{self, SerialLines};
11use crate::posix::termios;
12use crate::{
13 ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort,
14 SerialPortBuilder, StopBits,
15};
16
17fn close(fd: RawFd) {
20 let _ = ioctl::tiocnxcl(fd);
22
23 let _ = unistd::close(fd);
32}
33
34#[derive(Debug)]
61pub struct TTYPort {
62 fd: RawFd,
63 timeout: Duration,
64 exclusive: bool,
65 port_name: Option<String>,
66 #[cfg(any(target_os = "ios", target_os = "macos"))]
67 baud_rate: u32,
68}
69
70#[derive(Clone, Copy, Debug)]
72pub enum BreakDuration {
73 Short,
75 Arbitrary(std::num::NonZeroI32),
77}
78
79struct OwnedFd(RawFd);
84
85impl Drop for OwnedFd {
86 fn drop(&mut self) {
87 close(self.0);
88 }
89}
90
91impl OwnedFd {
92 fn into_raw(self) -> RawFd {
93 let fd = self.0;
94 mem::forget(self);
95 fd
96 }
97}
98
99impl TTYPort {
100 pub fn open(builder: &SerialPortBuilder) -> Result<TTYPort> {
118 use nix::fcntl::FcntlArg::F_SETFL;
119 use nix::libc::{cfmakeraw, tcgetattr, tcsetattr};
120
121 let path = Path::new(&builder.path);
122 let fd = OwnedFd(nix::fcntl::open(
123 path,
124 OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC,
125 nix::sys::stat::Mode::empty(),
126 )?);
127
128 ioctl::tiocexcl(fd.0)?;
132
133 let mut termios = MaybeUninit::uninit();
134 nix::errno::Errno::result(unsafe { tcgetattr(fd.0, termios.as_mut_ptr()) })?;
135 let mut termios = unsafe { termios.assume_init() };
136
137 termios.c_cflag |= libc::CREAD | libc::CLOCAL;
140 unsafe { cfmakeraw(&mut termios) };
144
145 unsafe { tcsetattr(fd.0, libc::TCSANOW, &termios) };
147
148 let mut actual_termios = MaybeUninit::uninit();
150 unsafe { tcgetattr(fd.0, actual_termios.as_mut_ptr()) };
151 let actual_termios = unsafe { actual_termios.assume_init() };
152
153 if actual_termios.c_iflag != termios.c_iflag
154 || actual_termios.c_oflag != termios.c_oflag
155 || actual_termios.c_lflag != termios.c_lflag
156 || actual_termios.c_cflag != termios.c_cflag
157 {
158 return Err(Error::new(
159 ErrorKind::Unknown,
160 "Settings did not apply correctly",
161 ));
162 };
163
164 #[cfg(any(target_os = "ios", target_os = "macos"))]
165 if builder.baud_rate > 0 {
166 unsafe { libc::tcflush(fd.0, libc::TCIOFLUSH) };
167 }
168
169 fcntl(fd.0, F_SETFL(nix::fcntl::OFlag::empty()))?;
171
172 let mut termios = termios::get_termios(fd.0)?;
174 termios::set_parity(&mut termios, builder.parity);
175 termios::set_flow_control(&mut termios, builder.flow_control);
176 termios::set_data_bits(&mut termios, builder.data_bits);
177 termios::set_stop_bits(&mut termios, builder.stop_bits);
178 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
179 termios::set_baud_rate(&mut termios, builder.baud_rate)?;
180 #[cfg(any(target_os = "ios", target_os = "macos"))]
181 termios::set_termios(fd.0, &termios, builder.baud_rate)?;
182 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
183 termios::set_termios(fd.0, &termios)?;
184
185 Ok(TTYPort {
187 fd: fd.into_raw(),
188 timeout: builder.timeout,
189 exclusive: true,
190 port_name: Some(builder.path.clone()),
191 #[cfg(any(target_os = "ios", target_os = "macos"))]
192 baud_rate: builder.baud_rate,
193 })
194 }
195
196 pub fn exclusive(&self) -> bool {
201 self.exclusive
202 }
203
204 pub fn set_exclusive(&mut self, exclusive: bool) -> Result<()> {
215 let setting_result = if exclusive {
216 ioctl::tiocexcl(self.fd)
217 } else {
218 ioctl::tiocnxcl(self.fd)
219 };
220
221 setting_result?;
222 self.exclusive = exclusive;
223 Ok(())
224 }
225
226 fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()> {
227 if level {
228 ioctl::tiocmbis(self.fd, pin)
229 } else {
230 ioctl::tiocmbic(self.fd, pin)
231 }
232 }
233
234 fn read_pin(&mut self, pin: ioctl::SerialLines) -> Result<bool> {
235 ioctl::tiocmget(self.fd).map(|pins| pins.contains(pin))
236 }
237
238 pub fn pair() -> Result<(Self, Self)> {
261 let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?;
263
264 nix::pty::grantpt(&next_pty_fd)?;
266
267 nix::pty::unlockpt(&next_pty_fd)?;
269
270 #[cfg(not(any(
272 target_os = "linux",
273 target_os = "android",
274 target_os = "emscripten",
275 target_os = "fuchsia"
276 )))]
277 let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? };
278
279 #[cfg(any(
280 target_os = "linux",
281 target_os = "android",
282 target_os = "emscripten",
283 target_os = "fuchsia"
284 ))]
285 let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?;
286
287 #[cfg(any(target_os = "ios", target_os = "macos"))]
289 let baud_rate = 9600;
290 let fd = nix::fcntl::open(
291 Path::new(&ptty_name),
292 OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
293 nix::sys::stat::Mode::empty(),
294 )?;
295
296 let mut termios = MaybeUninit::uninit();
298 let res = unsafe { crate::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) };
299 if let Err(e) = nix::errno::Errno::result(res) {
300 close(fd);
301 return Err(e.into());
302 }
303 let mut termios = unsafe { termios.assume_init() };
304 unsafe { crate::posix::tty::libc::cfmakeraw(&mut termios) };
305 unsafe { crate::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) };
306
307 fcntl(
308 fd,
309 nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()),
310 )?;
311
312 let slave_tty = TTYPort {
313 fd,
314 timeout: Duration::from_millis(100),
315 exclusive: true,
316 port_name: Some(ptty_name),
317 #[cfg(any(target_os = "ios", target_os = "macos"))]
318 baud_rate,
319 };
320
321 let master_tty = TTYPort {
325 fd: next_pty_fd.into_raw_fd(),
326 timeout: Duration::from_millis(100),
327 exclusive: true,
328 port_name: None,
329 #[cfg(any(target_os = "ios", target_os = "macos"))]
330 baud_rate,
331 };
332
333 Ok((master_tty, slave_tty))
334 }
335
336 pub fn send_break(&self, duration: BreakDuration) -> Result<()> {
338 match duration {
339 BreakDuration::Short => nix::sys::termios::tcsendbreak(self.fd, 0),
340 BreakDuration::Arbitrary(n) => nix::sys::termios::tcsendbreak(self.fd, n.get()),
341 }
342 .map_err(|e| e.into())
343 }
344
345 pub fn try_clone_native(&self) -> Result<TTYPort> {
360 let fd_cloned: i32 = fcntl(self.fd, nix::fcntl::F_DUPFD_CLOEXEC(self.fd))?;
361 Ok(TTYPort {
362 fd: fd_cloned,
363 exclusive: self.exclusive,
364 port_name: self.port_name.clone(),
365 timeout: self.timeout,
366 #[cfg(any(target_os = "ios", target_os = "macos"))]
367 baud_rate: self.baud_rate,
368 })
369 }
370}
371
372impl Drop for TTYPort {
373 fn drop(&mut self) {
374 close(self.fd);
375 }
376}
377
378impl AsRawFd for TTYPort {
379 fn as_raw_fd(&self) -> RawFd {
380 self.fd
381 }
382}
383
384impl IntoRawFd for TTYPort {
385 fn into_raw_fd(self) -> RawFd {
386 let TTYPort { fd, .. } = self;
390 mem::forget(self);
391 fd
392 }
393}
394
395#[cfg(any(target_os = "ios", target_os = "macos"))]
397fn get_termios_speed(fd: RawFd) -> u32 {
398 let mut termios = MaybeUninit::uninit();
399 let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
400 nix::errno::Errno::result(res).expect("Failed to get termios data");
401 let termios = unsafe { termios.assume_init() };
402 assert_eq!(termios.c_ospeed, termios.c_ispeed);
403 termios.c_ospeed as u32
404}
405
406impl FromRawFd for TTYPort {
407 unsafe fn from_raw_fd(fd: RawFd) -> Self {
408 TTYPort {
409 fd,
410 timeout: Duration::from_millis(100),
411 exclusive: ioctl::tiocexcl(fd).is_ok(),
412 port_name: None,
415 #[cfg(any(target_os = "ios", target_os = "macos"))]
419 baud_rate: get_termios_speed(fd),
420 }
421 }
422}
423
424impl io::Read for TTYPort {
425 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
426 if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) {
427 return Err(io::Error::from(Error::from(e)));
428 }
429
430 nix::unistd::read(self.fd, buf).map_err(|e| io::Error::from(Error::from(e)))
431 }
432}
433
434impl io::Write for TTYPort {
435 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
436 if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) {
437 return Err(io::Error::from(Error::from(e)));
438 }
439
440 nix::unistd::write(self.fd, buf).map_err(|e| io::Error::from(Error::from(e)))
441 }
442
443 fn flush(&mut self) -> io::Result<()> {
444 nix::sys::termios::tcdrain(self.fd)
445 .map_err(|_| io::Error::new(io::ErrorKind::Other, "flush failed"))
446 }
447}
448
449impl SerialPort for TTYPort {
450 fn name(&self) -> Option<String> {
451 self.port_name.clone()
452 }
453
454 #[cfg(any(
459 target_os = "android",
460 all(
461 target_os = "linux",
462 not(any(
463 target_env = "musl",
464 target_arch = "powerpc",
465 target_arch = "powerpc64"
466 ))
467 )
468 ))]
469 fn baud_rate(&self) -> Result<u32> {
470 let termios2 = ioctl::tcgets2(self.fd)?;
471
472 assert!(termios2.c_ospeed == termios2.c_ispeed);
473
474 Ok(termios2.c_ospeed)
475 }
476
477 #[cfg(any(
482 target_os = "dragonfly",
483 target_os = "freebsd",
484 target_os = "netbsd",
485 target_os = "openbsd"
486 ))]
487 fn baud_rate(&self) -> Result<u32> {
488 let termios = termios::get_termios(self.fd)?;
489
490 let ospeed = unsafe { libc::cfgetospeed(&termios) };
491 let ispeed = unsafe { libc::cfgetispeed(&termios) };
492
493 assert!(ospeed == ispeed);
494
495 Ok(ospeed as u32)
496 }
497
498 #[cfg(any(target_os = "ios", target_os = "macos"))]
503 fn baud_rate(&self) -> Result<u32> {
504 Ok(self.baud_rate)
505 }
506
507 #[cfg(all(
512 target_os = "linux",
513 any(
514 target_env = "musl",
515 target_arch = "powerpc",
516 target_arch = "powerpc64"
517 )
518 ))]
519 fn baud_rate(&self) -> Result<u32> {
520 use self::libc::{
521 B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000,
522 B460800, B500000, B576000, B921600,
523 };
524 use self::libc::{
525 B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400, B300, B38400,
526 B4800, B50, B57600, B600, B75, B9600,
527 };
528
529 let termios = termios::get_termios(self.fd)?;
530 let ospeed = unsafe { libc::cfgetospeed(&termios) };
531 let ispeed = unsafe { libc::cfgetispeed(&termios) };
532
533 assert!(ospeed == ispeed);
534
535 let res: u32 = match ospeed {
536 B50 => 50,
537 B75 => 75,
538 B110 => 110,
539 B134 => 134,
540 B150 => 150,
541 B200 => 200,
542 B300 => 300,
543 B600 => 600,
544 B1200 => 1200,
545 B1800 => 1800,
546 B2400 => 2400,
547 B4800 => 4800,
548 B9600 => 9600,
549 B19200 => 19_200,
550 B38400 => 38_400,
551 B57600 => 57_600,
552 B115200 => 115_200,
553 B230400 => 230_400,
554 B460800 => 460_800,
555 B500000 => 500_000,
556 B576000 => 576_000,
557 B921600 => 921_600,
558 B1000000 => 1_000_000,
559 B1152000 => 1_152_000,
560 B1500000 => 1_500_000,
561 B2000000 => 2_000_000,
562 B2500000 => 2_500_000,
563 B3000000 => 3_000_000,
564 B3500000 => 3_500_000,
565 B4000000 => 4_000_000,
566 _ => unreachable!(),
567 };
568
569 Ok(res)
570 }
571
572 fn data_bits(&self) -> Result<DataBits> {
573 let termios = termios::get_termios(self.fd)?;
574 match termios.c_cflag & libc::CSIZE {
575 libc::CS8 => Ok(DataBits::Eight),
576 libc::CS7 => Ok(DataBits::Seven),
577 libc::CS6 => Ok(DataBits::Six),
578 libc::CS5 => Ok(DataBits::Five),
579 _ => Err(Error::new(
580 ErrorKind::Unknown,
581 "Invalid data bits setting encountered",
582 )),
583 }
584 }
585
586 fn flow_control(&self) -> Result<FlowControl> {
587 let termios = termios::get_termios(self.fd)?;
588 if termios.c_cflag & libc::CRTSCTS == libc::CRTSCTS {
589 Ok(FlowControl::Hardware)
590 } else if termios.c_iflag & (libc::IXON | libc::IXOFF) == (libc::IXON | libc::IXOFF) {
591 Ok(FlowControl::Software)
592 } else {
593 Ok(FlowControl::None)
594 }
595 }
596
597 fn parity(&self) -> Result<Parity> {
598 let termios = termios::get_termios(self.fd)?;
599 if termios.c_cflag & libc::PARENB == libc::PARENB {
600 if termios.c_cflag & libc::PARODD == libc::PARODD {
601 Ok(Parity::Odd)
602 } else {
603 Ok(Parity::Even)
604 }
605 } else {
606 Ok(Parity::None)
607 }
608 }
609
610 fn stop_bits(&self) -> Result<StopBits> {
611 let termios = termios::get_termios(self.fd)?;
612 if termios.c_cflag & libc::CSTOPB == libc::CSTOPB {
613 Ok(StopBits::Two)
614 } else {
615 Ok(StopBits::One)
616 }
617 }
618
619 fn timeout(&self) -> Duration {
620 self.timeout
621 }
622
623 #[cfg(any(
624 target_os = "android",
625 target_os = "dragonfly",
626 target_os = "freebsd",
627 target_os = "netbsd",
628 target_os = "openbsd",
629 target_os = "linux"
630 ))]
631 fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
632 let mut termios = termios::get_termios(self.fd)?;
633 termios::set_baud_rate(&mut termios, baud_rate)?;
634 termios::set_termios(self.fd, &termios)
635 }
636
637 #[cfg(any(target_os = "ios", target_os = "macos"))]
639 fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
640 ioctl::iossiospeed(self.fd, &(baud_rate as libc::speed_t))?;
641 self.baud_rate = baud_rate;
642 Ok(())
643 }
644
645 fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> {
646 let mut termios = termios::get_termios(self.fd)?;
647 termios::set_flow_control(&mut termios, flow_control);
648 #[cfg(any(target_os = "ios", target_os = "macos"))]
649 return termios::set_termios(self.fd, &termios, self.baud_rate);
650 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
651 return termios::set_termios(self.fd, &termios);
652 }
653
654 fn set_parity(&mut self, parity: Parity) -> Result<()> {
655 let mut termios = termios::get_termios(self.fd)?;
656 termios::set_parity(&mut termios, parity);
657 #[cfg(any(target_os = "ios", target_os = "macos"))]
658 return termios::set_termios(self.fd, &termios, self.baud_rate);
659 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
660 return termios::set_termios(self.fd, &termios);
661 }
662
663 fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> {
664 let mut termios = termios::get_termios(self.fd)?;
665 termios::set_data_bits(&mut termios, data_bits);
666 #[cfg(any(target_os = "ios", target_os = "macos"))]
667 return termios::set_termios(self.fd, &termios, self.baud_rate);
668 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
669 return termios::set_termios(self.fd, &termios);
670 }
671
672 fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> {
673 let mut termios = termios::get_termios(self.fd)?;
674 termios::set_stop_bits(&mut termios, stop_bits);
675 #[cfg(any(target_os = "ios", target_os = "macos"))]
676 return termios::set_termios(self.fd, &termios, self.baud_rate);
677 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
678 return termios::set_termios(self.fd, &termios);
679 }
680
681 fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
682 self.timeout = timeout;
683 Ok(())
684 }
685
686 fn write_request_to_send(&mut self, level: bool) -> Result<()> {
687 self.set_pin(SerialLines::REQUEST_TO_SEND, level)
688 }
689
690 fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> {
691 self.set_pin(SerialLines::DATA_TERMINAL_READY, level)
692 }
693
694 fn read_clear_to_send(&mut self) -> Result<bool> {
695 self.read_pin(SerialLines::CLEAR_TO_SEND)
696 }
697
698 fn read_data_set_ready(&mut self) -> Result<bool> {
699 self.read_pin(SerialLines::DATA_SET_READY)
700 }
701
702 fn read_ring_indicator(&mut self) -> Result<bool> {
703 self.read_pin(SerialLines::RING)
704 }
705
706 fn read_carrier_detect(&mut self) -> Result<bool> {
707 self.read_pin(SerialLines::DATA_CARRIER_DETECT)
708 }
709
710 fn bytes_to_read(&self) -> Result<u32> {
711 ioctl::fionread(self.fd)
712 }
713
714 fn bytes_to_write(&self) -> Result<u32> {
715 ioctl::tiocoutq(self.fd)
716 }
717
718 fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> {
719 let buffer_id = match buffer_to_clear {
720 ClearBuffer::Input => libc::TCIFLUSH,
721 ClearBuffer::Output => libc::TCOFLUSH,
722 ClearBuffer::All => libc::TCIOFLUSH,
723 };
724
725 let res = unsafe { nix::libc::tcflush(self.fd, buffer_id) };
726
727 nix::errno::Errno::result(res)
728 .map(|_| ())
729 .map_err(|e| e.into())
730 }
731
732 fn try_clone(&self) -> Result<Box<dyn SerialPort>> {
733 match self.try_clone_native() {
734 Ok(p) => Ok(Box::new(p)),
735 Err(e) => Err(e),
736 }
737 }
738
739 fn set_break(&self) -> Result<()> {
740 ioctl::tiocsbrk(self.fd)
741 }
742
743 fn clear_break(&self) -> Result<()> {
744 ioctl::tioccbrk(self.fd)
745 }
746}
747
748#[test]
749fn test_ttyport_into_raw_fd() {
750 #![allow(unused_variables)]
755 let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
756
757 let master_fd = master.into_raw_fd();
759 let mut termios = MaybeUninit::uninit();
760 let res = unsafe { nix::libc::tcgetattr(master_fd, termios.as_mut_ptr()) };
761 if res != 0 {
762 close(master_fd);
763 panic!("tcgetattr on the master port failed");
764 }
765
766 let slave_fd = slave.into_raw_fd();
768 let res = unsafe { nix::libc::tcgetattr(slave_fd, termios.as_mut_ptr()) };
769 if res != 0 {
770 close(slave_fd);
771 panic!("tcgetattr on the master port failed");
772 }
773 close(master_fd);
774 close(slave_fd);
775}