1use std::mem::MaybeUninit;
2use std::os::unix::prelude::*;
3use std::path::Path;
4use std::time::{Duration, Instant};
5use std::{io, mem};
6
7use nix::errno::Errno;
8use nix::fcntl::{fcntl, FcntlArg, OFlag};
9use nix::{libc, unistd};
10
11use crate::posix::flock;
12use crate::posix::ioctl::{self, SerialLines};
13use crate::posix::termios;
14use crate::{
15 ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort,
16 SerialPortBuilder, StopBits,
17};
18
19fn close(fd: RawFd) {
22 let _ = ioctl::tiocnxcl(fd);
25 let _ = unistd::close(fd);
38}
39
40#[derive(Debug)]
67pub struct TTYPort {
68 fd: RawFd,
69 timeout: Duration,
70 exclusive: bool,
71 port_name: Option<String>,
72 #[cfg(any(target_os = "ios", target_os = "macos"))]
73 baud_rate: u32,
74}
75
76#[derive(Clone, Copy, Debug)]
78pub enum BreakDuration {
79 Short,
81 Arbitrary(std::num::NonZeroI32),
83}
84
85struct OwnedFd(RawFd);
90
91impl Drop for OwnedFd {
92 fn drop(&mut self) {
93 close(self.0);
94 }
95}
96
97impl OwnedFd {
98 fn into_raw_fd(self) -> RawFd {
99 let fd = self.0;
100 mem::forget(self);
101 fd
102 }
103}
104
105impl TTYPort {
106 pub fn open(builder: &SerialPortBuilder) -> Result<TTYPort> {
124 let path = Path::new(&builder.path);
125 let fd = OwnedFd(nix::fcntl::open(
126 path,
127 OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC,
128 nix::sys::stat::Mode::empty(),
129 )?);
130
131 if builder.exclusive {
135 ioctl::tiocexcl(fd.0)?;
136 flock::lock_exclusive(fd.0)?;
137 } else {
138 flock::lock_shared(fd.0)?;
139 }
140 let mut termios = MaybeUninit::uninit();
141 Errno::result(unsafe { libc::tcgetattr(fd.0, termios.as_mut_ptr()) })?;
142 let mut termios = unsafe { termios.assume_init() };
143
144 termios.c_cflag |= libc::CREAD | libc::CLOCAL;
147 unsafe { libc::cfmakeraw(&mut termios) };
151
152 Errno::result(unsafe { libc::tcsetattr(fd.0, libc::TCSANOW, &termios) })?;
154
155 let mut actual_termios = MaybeUninit::uninit();
157 Errno::result(unsafe { libc::tcgetattr(fd.0, actual_termios.as_mut_ptr()) })?;
158 let actual_termios = unsafe { actual_termios.assume_init() };
159
160 if actual_termios.c_iflag != termios.c_iflag
161 || actual_termios.c_oflag != termios.c_oflag
162 || actual_termios.c_lflag != termios.c_lflag
163 || actual_termios.c_cflag != termios.c_cflag
164 {
165 return Err(Error::new(
166 ErrorKind::Unknown,
167 "Settings did not apply correctly",
168 ));
169 };
170
171 #[cfg(any(target_os = "ios", target_os = "macos"))]
172 if builder.baud_rate > 0 {
173 Errno::result(unsafe { libc::tcflush(fd.0, libc::TCIOFLUSH) })?;
174 }
175
176 fcntl(fd.0, FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()))?;
178
179 let mut termios = termios::get_termios(fd.0)?;
181 termios::set_parity(&mut termios, builder.parity);
182 termios::set_flow_control(&mut termios, builder.flow_control);
183 termios::set_data_bits(&mut termios, builder.data_bits);
184 termios::set_stop_bits(&mut termios, builder.stop_bits);
185 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
186 termios::set_baud_rate(&mut termios, builder.baud_rate)?;
187 #[cfg(any(target_os = "ios", target_os = "macos"))]
188 termios::set_termios(fd.0, &termios, builder.baud_rate)?;
189 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
190 termios::set_termios(fd.0, &termios)?;
191
192 let mut port = TTYPort {
194 fd: fd.into_raw_fd(),
195 timeout: builder.timeout,
196 exclusive: builder.exclusive,
197 port_name: Some(builder.path.clone()),
198 #[cfg(any(target_os = "ios", target_os = "macos"))]
199 baud_rate: builder.baud_rate,
200 };
201
202 if builder.baud_rate > 0 {
205 if let Some(dtr) = builder.dtr_on_open {
206 let _ = port.write_data_terminal_ready(dtr);
207 }
208 }
209
210 Ok(port)
211 }
212
213 pub fn exclusive(&self) -> bool {
218 self.exclusive
219 }
220
221 pub fn set_exclusive(&mut self, exclusive: bool) -> Result<()> {
238 let setting_result = if exclusive {
239 ioctl::tiocexcl(self.fd)
240 } else {
241 ioctl::tiocnxcl(self.fd)
242 };
243
244 setting_result?;
245
246 let flock_result = if exclusive {
247 flock::lock_exclusive(self.fd)
248 } else {
249 flock::lock_shared(self.fd)
250 };
251
252 flock_result?;
253
254 self.exclusive = exclusive;
255 Ok(())
256 }
257
258 fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()> {
259 if level {
260 ioctl::tiocmbis(self.fd, pin)
261 } else {
262 ioctl::tiocmbic(self.fd, pin)
263 }
264 }
265
266 fn read_pin(&mut self, pin: ioctl::SerialLines) -> Result<bool> {
267 ioctl::tiocmget(self.fd).map(|pins| pins.contains(pin))
268 }
269
270 pub fn pair() -> Result<(Self, Self)> {
293 let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?;
295
296 nix::pty::grantpt(&next_pty_fd)?;
298
299 nix::pty::unlockpt(&next_pty_fd)?;
301
302 #[cfg(not(any(
304 target_os = "linux",
305 target_os = "android",
306 target_os = "emscripten",
307 target_os = "fuchsia"
308 )))]
309 let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? };
310
311 #[cfg(any(
312 target_os = "linux",
313 target_os = "android",
314 target_os = "emscripten",
315 target_os = "fuchsia"
316 ))]
317 let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?;
318
319 #[cfg(any(target_os = "ios", target_os = "macos"))]
321 let baud_rate = 9600;
322
323 let fd = OwnedFd(nix::fcntl::open(
325 Path::new(&ptty_name),
326 OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
327 nix::sys::stat::Mode::empty(),
328 )?);
329
330 let mut termios = MaybeUninit::uninit();
332 Errno::result(unsafe { libc::tcgetattr(fd.0, termios.as_mut_ptr()) })?;
333
334 let mut termios = unsafe { termios.assume_init() };
335 unsafe { libc::cfmakeraw(&mut termios) };
336 Errno::result(unsafe { libc::tcsetattr(fd.0, libc::TCSANOW, &termios) })?;
337
338 fcntl(
339 fd.0,
340 nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()),
341 )?;
342
343 let slave_tty = TTYPort {
344 fd: fd.into_raw_fd(),
345 timeout: Duration::from_millis(100),
346 exclusive: true,
347 port_name: Some(ptty_name),
348 #[cfg(any(target_os = "ios", target_os = "macos"))]
349 baud_rate,
350 };
351
352 let master_tty = TTYPort {
356 fd: next_pty_fd.into_raw_fd(),
357 timeout: Duration::from_millis(100),
358 exclusive: true,
359 port_name: None,
360 #[cfg(any(target_os = "ios", target_os = "macos"))]
361 baud_rate,
362 };
363
364 Ok((master_tty, slave_tty))
365 }
366
367 pub fn send_break(&self, duration: BreakDuration) -> Result<()> {
369 match duration {
370 BreakDuration::Short => nix::sys::termios::tcsendbreak(self.fd, 0),
371 BreakDuration::Arbitrary(n) => nix::sys::termios::tcsendbreak(self.fd, n.get()),
372 }
373 .map_err(|e| e.into())
374 }
375
376 pub fn try_clone_native(&self) -> Result<TTYPort> {
391 let fd_cloned: i32 = fcntl(self.fd, nix::fcntl::F_DUPFD_CLOEXEC(self.fd))?;
392 Ok(TTYPort {
393 fd: fd_cloned,
394 exclusive: self.exclusive,
395 port_name: self.port_name.clone(),
396 timeout: self.timeout,
397 #[cfg(any(target_os = "ios", target_os = "macos"))]
398 baud_rate: self.baud_rate,
399 })
400 }
401}
402
403impl Drop for TTYPort {
404 fn drop(&mut self) {
405 close(self.fd);
406 }
407}
408
409impl AsRawFd for TTYPort {
410 fn as_raw_fd(&self) -> RawFd {
411 self.fd
412 }
413}
414
415impl IntoRawFd for TTYPort {
416 fn into_raw_fd(self) -> RawFd {
417 let TTYPort { fd, .. } = self;
421 mem::forget(self);
422 fd
423 }
424}
425
426#[cfg(any(target_os = "ios", target_os = "macos"))]
428fn get_termios_speed(fd: RawFd) -> u32 {
429 let mut termios = MaybeUninit::uninit();
430 Errno::result(unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })
432 .expect("Failed to get termios data");
433 let termios = unsafe { termios.assume_init() };
434 assert_eq!(termios.c_ospeed, termios.c_ispeed);
435 termios.c_ospeed as u32
436}
437
438impl FromRawFd for TTYPort {
439 unsafe fn from_raw_fd(fd: RawFd) -> Self {
440 let flock_successful = flock::lock_exclusive(fd).is_ok();
441
442 let tiocexcl_successful = ioctl::tiocexcl(fd).is_ok();
449
450 TTYPort {
451 fd,
452 timeout: Duration::from_millis(100),
453 exclusive: tiocexcl_successful && flock_successful,
454 port_name: None,
457 #[cfg(any(target_os = "ios", target_os = "macos"))]
461 baud_rate: get_termios_speed(fd),
462 }
463 }
464}
465
466impl io::Read for TTYPort {
467 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
468 if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) {
469 return Err(io::Error::from(Error::from(e)));
470 }
471
472 nix::unistd::read(self.fd, buf).map_err(|e| io::Error::from(Error::from(e)))
473 }
474}
475
476impl io::Write for TTYPort {
477 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
478 if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) {
479 return Err(io::Error::from(Error::from(e)));
480 }
481
482 nix::unistd::write(self.fd, buf).map_err(|e| io::Error::from(Error::from(e)))
483 }
484
485 fn flush(&mut self) -> io::Result<()> {
486 let timeout = Instant::now() + self.timeout;
487 loop {
488 return match nix::sys::termios::tcdrain(self.fd) {
489 Ok(_) => Ok(()),
490 Err(Errno::EINTR) => {
491 if Instant::now() < timeout {
494 continue;
495 } else {
496 Err(io::Error::new(
497 io::ErrorKind::TimedOut,
498 "timeout for retrying flush reached",
499 ))
500 }
501 }
502 Err(_) => Err(io::Error::new(io::ErrorKind::Other, "flush failed")),
503 };
504 }
505 }
506}
507
508impl SerialPort for TTYPort {
509 fn name(&self) -> Option<String> {
510 self.port_name.clone()
511 }
512
513 #[cfg(any(
518 target_os = "android",
519 all(
520 target_os = "linux",
521 not(any(target_arch = "powerpc", target_arch = "powerpc64"))
522 )
523 ))]
524 fn baud_rate(&self) -> Result<u32> {
525 let termios2 = ioctl::tcgets2(self.fd)?;
526
527 assert!(termios2.c_ospeed == termios2.c_ispeed);
528
529 Ok(termios2.c_ospeed)
530 }
531
532 #[cfg(any(
537 target_os = "dragonfly",
538 target_os = "freebsd",
539 target_os = "netbsd",
540 target_os = "openbsd"
541 ))]
542 fn baud_rate(&self) -> Result<u32> {
543 let termios = termios::get_termios(self.fd)?;
544
545 let ospeed = unsafe { libc::cfgetospeed(&termios) };
546 let ispeed = unsafe { libc::cfgetispeed(&termios) };
547
548 assert!(ospeed == ispeed);
549
550 Ok(ospeed as u32)
551 }
552
553 #[cfg(any(target_os = "ios", target_os = "macos"))]
558 fn baud_rate(&self) -> Result<u32> {
559 Ok(self.baud_rate)
560 }
561
562 #[cfg(all(
567 target_os = "linux",
568 any(target_arch = "powerpc", target_arch = "powerpc64")
569 ))]
570 fn baud_rate(&self) -> Result<u32> {
571 use libc::{
572 B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000,
573 B460800, B500000, B576000, B921600,
574 };
575 use libc::{
576 B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400, B300, B38400,
577 B4800, B50, B57600, B600, B75, B9600,
578 };
579
580 let termios = termios::get_termios(self.fd)?;
581 let ospeed = unsafe { libc::cfgetospeed(&termios) };
582 let ispeed = unsafe { libc::cfgetispeed(&termios) };
583
584 assert!(ospeed == ispeed);
585
586 let res: u32 = match ospeed {
587 B50 => 50,
588 B75 => 75,
589 B110 => 110,
590 B134 => 134,
591 B150 => 150,
592 B200 => 200,
593 B300 => 300,
594 B600 => 600,
595 B1200 => 1200,
596 B1800 => 1800,
597 B2400 => 2400,
598 B4800 => 4800,
599 B9600 => 9600,
600 B19200 => 19_200,
601 B38400 => 38_400,
602 B57600 => 57_600,
603 B115200 => 115_200,
604 B230400 => 230_400,
605 B460800 => 460_800,
606 B500000 => 500_000,
607 B576000 => 576_000,
608 B921600 => 921_600,
609 B1000000 => 1_000_000,
610 B1152000 => 1_152_000,
611 B1500000 => 1_500_000,
612 B2000000 => 2_000_000,
613 B2500000 => 2_500_000,
614 B3000000 => 3_000_000,
615 B3500000 => 3_500_000,
616 B4000000 => 4_000_000,
617 _ => unreachable!(),
618 };
619
620 Ok(res)
621 }
622
623 fn data_bits(&self) -> Result<DataBits> {
624 let termios = termios::get_termios(self.fd)?;
625 match termios.c_cflag & libc::CSIZE {
626 libc::CS8 => Ok(DataBits::Eight),
627 libc::CS7 => Ok(DataBits::Seven),
628 libc::CS6 => Ok(DataBits::Six),
629 libc::CS5 => Ok(DataBits::Five),
630 _ => Err(Error::new(
631 ErrorKind::Unknown,
632 "Invalid data bits setting encountered",
633 )),
634 }
635 }
636
637 fn flow_control(&self) -> Result<FlowControl> {
638 let termios = termios::get_termios(self.fd)?;
639 if termios.c_cflag & libc::CRTSCTS == libc::CRTSCTS {
640 Ok(FlowControl::Hardware)
641 } else if termios.c_iflag & (libc::IXON | libc::IXOFF) == (libc::IXON | libc::IXOFF) {
642 Ok(FlowControl::Software)
643 } else {
644 Ok(FlowControl::None)
645 }
646 }
647
648 fn parity(&self) -> Result<Parity> {
649 let termios = termios::get_termios(self.fd)?;
650 if termios.c_cflag & libc::PARENB == libc::PARENB {
651 if termios.c_cflag & libc::PARODD == libc::PARODD {
652 Ok(Parity::Odd)
653 } else {
654 Ok(Parity::Even)
655 }
656 } else {
657 Ok(Parity::None)
658 }
659 }
660
661 fn stop_bits(&self) -> Result<StopBits> {
662 let termios = termios::get_termios(self.fd)?;
663 if termios.c_cflag & libc::CSTOPB == libc::CSTOPB {
664 Ok(StopBits::Two)
665 } else {
666 Ok(StopBits::One)
667 }
668 }
669
670 fn timeout(&self) -> Duration {
671 self.timeout
672 }
673
674 #[cfg(any(
675 target_os = "android",
676 target_os = "dragonfly",
677 target_os = "freebsd",
678 target_os = "netbsd",
679 target_os = "openbsd",
680 target_os = "linux"
681 ))]
682 fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
683 let mut termios = termios::get_termios(self.fd)?;
684 termios::set_baud_rate(&mut termios, baud_rate)?;
685 termios::set_termios(self.fd, &termios)
686 }
687
688 #[cfg(any(target_os = "ios", target_os = "macos"))]
690 fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
691 ioctl::iossiospeed(self.fd, &(baud_rate as libc::speed_t))?;
692 self.baud_rate = baud_rate;
693 Ok(())
694 }
695
696 fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> {
697 let mut termios = termios::get_termios(self.fd)?;
698 termios::set_flow_control(&mut termios, flow_control);
699 #[cfg(any(target_os = "ios", target_os = "macos"))]
700 return termios::set_termios(self.fd, &termios, self.baud_rate);
701 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
702 return termios::set_termios(self.fd, &termios);
703 }
704
705 fn set_parity(&mut self, parity: Parity) -> Result<()> {
706 let mut termios = termios::get_termios(self.fd)?;
707 termios::set_parity(&mut termios, parity);
708 #[cfg(any(target_os = "ios", target_os = "macos"))]
709 return termios::set_termios(self.fd, &termios, self.baud_rate);
710 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
711 return termios::set_termios(self.fd, &termios);
712 }
713
714 fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> {
715 let mut termios = termios::get_termios(self.fd)?;
716 termios::set_data_bits(&mut termios, data_bits);
717 #[cfg(any(target_os = "ios", target_os = "macos"))]
718 return termios::set_termios(self.fd, &termios, self.baud_rate);
719 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
720 return termios::set_termios(self.fd, &termios);
721 }
722
723 fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> {
724 let mut termios = termios::get_termios(self.fd)?;
725 termios::set_stop_bits(&mut termios, stop_bits);
726 #[cfg(any(target_os = "ios", target_os = "macos"))]
727 return termios::set_termios(self.fd, &termios, self.baud_rate);
728 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
729 return termios::set_termios(self.fd, &termios);
730 }
731
732 fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
733 self.timeout = timeout;
734 Ok(())
735 }
736
737 fn write_request_to_send(&mut self, level: bool) -> Result<()> {
738 self.set_pin(SerialLines::REQUEST_TO_SEND, level)
739 }
740
741 fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> {
742 self.set_pin(SerialLines::DATA_TERMINAL_READY, level)
743 }
744
745 fn read_clear_to_send(&mut self) -> Result<bool> {
746 self.read_pin(SerialLines::CLEAR_TO_SEND)
747 }
748
749 fn read_data_set_ready(&mut self) -> Result<bool> {
750 self.read_pin(SerialLines::DATA_SET_READY)
751 }
752
753 fn read_ring_indicator(&mut self) -> Result<bool> {
754 self.read_pin(SerialLines::RING)
755 }
756
757 fn read_carrier_detect(&mut self) -> Result<bool> {
758 self.read_pin(SerialLines::DATA_CARRIER_DETECT)
759 }
760
761 fn bytes_to_read(&self) -> Result<u32> {
762 ioctl::fionread(self.fd)
763 }
764
765 fn bytes_to_write(&self) -> Result<u32> {
766 ioctl::tiocoutq(self.fd)
767 }
768
769 fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> {
770 let buffer_id = match buffer_to_clear {
771 ClearBuffer::Input => libc::TCIFLUSH,
772 ClearBuffer::Output => libc::TCOFLUSH,
773 ClearBuffer::All => libc::TCIOFLUSH,
774 };
775
776 Errno::result(unsafe { libc::tcflush(self.fd, buffer_id) })
777 .map(|_| ())
778 .map_err(|e| e.into())
779 }
780
781 fn try_clone(&self) -> Result<Box<dyn SerialPort>> {
782 match self.try_clone_native() {
783 Ok(p) => Ok(Box::new(p)),
784 Err(e) => Err(e),
785 }
786 }
787
788 fn set_break(&self) -> Result<()> {
789 ioctl::tiocsbrk(self.fd)
790 }
791
792 fn clear_break(&self) -> Result<()> {
793 ioctl::tioccbrk(self.fd)
794 }
795}
796
797#[test]
798fn test_ttyport_into_raw_fd() {
799 #![allow(unused_variables)]
804 let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
805
806 let master_fd = master.into_raw_fd();
808 let mut termios = MaybeUninit::uninit();
809 let res = unsafe { libc::tcgetattr(master_fd, termios.as_mut_ptr()) };
810 if res != 0 {
811 close(master_fd);
812 panic!("tcgetattr on the master port failed");
813 }
814
815 let slave_fd = slave.into_raw_fd();
817 let res = unsafe { libc::tcgetattr(slave_fd, termios.as_mut_ptr()) };
818 if res != 0 {
819 close(slave_fd);
820 panic!("tcgetattr on the master port failed");
821 }
822 close(master_fd);
823 close(slave_fd);
824}