Skip to main content

serialport/posix/
tty.rs

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
19/// Convenience method for removing exclusive access from
20/// a fd and closing it.
21fn close(fd: RawFd) {
22    // Remove exclusive access on best-effort. There is no documentation hinting at `TIOCEXCL`
23    // being cleared automatically so explicitly attempt it here.
24    let _ = ioctl::tiocnxcl(fd);
25    // However, it's documented for `flock` that the file will be unlocked when all filedescriptors
26    // are `close()`d. So don't bother with releasing the flock - we're going to close the
27    // filedescriptor immediately.
28
29    // On Linux and BSD, we don't need to worry about return
30    // type as EBADF means the fd was never open or is already closed
31    //
32    // Linux and BSD guarantee that for any other error code the
33    // fd is already closed, though MacOSX does not.
34    //
35    // close() also should never be retried, and the error code
36    // in most cases in purely informative
37    let _ = unistd::close(fd);
38}
39
40/// A serial port implementation for POSIX TTY ports
41///
42/// The port will be closed when the value is dropped. This struct
43/// should not be instantiated directly by using `TTYPort::open()`.
44/// Instead, use the cross-platform `serialport::new()`. Example:
45///
46/// ```no_run
47/// let mut port = serialport::new("/dev/ttyS0", 115200).open().expect("Unable to open");
48/// # let _ = &mut port;
49/// ```
50///
51/// Note: on macOS, when connecting to a pseudo-terminal (`pty` opened via
52/// `posix_openpt`), the `baud_rate` should be set to 0; this will be used to
53/// explicitly _skip_ an attempt to set the baud rate of the file descriptor
54/// that would otherwise happen via an `ioctl` command.
55///
56/// ```no_run
57/// use serialport::{TTYPort, SerialPort};
58///
59/// let (mut master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair");
60/// # let _ = &mut master;
61/// # let _ = &mut slave;
62/// // ... elsewhere
63/// let mut port = TTYPort::open(&serialport::new(slave.name().unwrap(), 0)).expect("Unable to open");
64/// # let _ = &mut port;
65/// ```
66#[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/// Specifies the duration of a transmission break
77#[derive(Clone, Copy, Debug)]
78pub enum BreakDuration {
79    /// 0.25-0.5s
80    Short,
81    /// Specifies a break duration that is platform-dependent
82    Arbitrary(std::num::NonZeroI32),
83}
84
85/// Wrapper for RawFd to assure that it's properly closed,
86/// even if the enclosing function exits early.
87///
88/// This is similar to the (nightly-only) std::os::unix::io::OwnedFd.
89struct 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    /// Opens a TTY device as a serial port.
107    ///
108    /// `path` should be the path to a TTY device, e.g., `/dev/ttyS0`.
109    ///
110    /// Ports are opened in exclusive mode by default. If this is undesirable
111    /// behavior, use `TTYPort::set_exclusive(false)`.
112    ///
113    /// If the port settings differ from the default settings, characters received
114    /// before the new settings become active may be garbled. To remove those
115    /// from the receive buffer, call `TTYPort::clear(ClearBuffer::Input)`.
116    ///
117    /// ## Errors
118    ///
119    /// * `NoDevice` if the device could not be opened. This could indicate that
120    ///   the device is already in use.
121    /// * `InvalidInput` if `path` is not a valid device name.
122    /// * `Io` for any other error while opening or initializing the device.
123    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        // Set the requested access mode on the port. In exclusive mode use
132        // TIOCEXCL and an exclusive flock to prevent other openers. In shared
133        // mode we only need a shared flock to allow concurrent access.
134        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        // setup TTY for binary serial port access
145        // Enable reading from the port and ignore all modem control lines
146        termios.c_cflag |= libc::CREAD | libc::CLOCAL;
147        // Enable raw mode which disables any implicit processing of the input or output data streams
148        // This also sets no timeout period and a read will block until at least one character is
149        // available.
150        unsafe { libc::cfmakeraw(&mut termios) };
151
152        // write settings to TTY
153        Errno::result(unsafe { libc::tcsetattr(fd.0, libc::TCSANOW, &termios) })?;
154
155        // Read back settings from port and confirm they were applied correctly
156        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        // clear O_NONBLOCK flag
177        fcntl(fd.0, FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()))?;
178
179        // Configure the low-level port settings
180        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        // Return the final port object
193        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        // Ignore setting DTR for pseudo terminals. This might be indicated by baud_rate == 0, but
203        // as this is not always the case, just try on best-effort.
204        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    /// Returns the exclusivity of the port
214    ///
215    /// If a port is exclusive, then trying to open the same device path again
216    /// will fail.
217    pub fn exclusive(&self) -> bool {
218        self.exclusive
219    }
220
221    /// Sets the exclusivity of the port
222    ///
223    /// If a port is exclusive, then trying to open the same device path again
224    /// will fail.
225    ///
226    /// The tiocexcl ioctl is used to prevent other applications from opening
227    /// the port.
228    ///
229    /// `flock` is used to place an advisory lock, which prevents conflicts with
230    /// other applications using `flock`.
231    ///
232    /// See the man pages for the tiocexcl/tiocnxcl ioctl's and `flock` for more details.
233    ///
234    /// ## Errors
235    ///
236    /// * `Io` for any error while setting exclusivity for the port.
237    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    /// Create a pair of pseudo serial terminals
271    ///
272    /// ## Returns
273    /// Two connected `TTYPort` objects: `(master, slave)`
274    ///
275    /// ## Errors
276    /// Attempting any IO or parameter settings on the slave tty after the master
277    /// tty is closed will return errors.
278    ///
279    /// On some platforms manipulating the master port will fail and only
280    /// modifying the slave port is possible.
281    ///
282    /// ## Examples
283    ///
284    /// ```
285    /// use serialport::TTYPort;
286    ///
287    /// let (mut master, mut slave) = TTYPort::pair().unwrap();
288    ///
289    /// # let _ = &mut master;
290    /// # let _ = &mut slave;
291    /// ```
292    pub fn pair() -> Result<(Self, Self)> {
293        // Open the next free pty.
294        let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?;
295
296        // Grant access to the associated slave pty
297        nix::pty::grantpt(&next_pty_fd)?;
298
299        // Unlock the slave pty
300        nix::pty::unlockpt(&next_pty_fd)?;
301
302        // Get the path of the attached slave ptty
303        #[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        // Open the slave port
320        #[cfg(any(target_os = "ios", target_os = "macos"))]
321        let baud_rate = 9600;
322
323        // Wrap the slave fd in `OwnedFd` immediately so it auto-closes on error
324        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        // Set the port to a raw state. Using these ports will not work without this.
331        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        // Manually construct the master port here because the
353        // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other
354        // BSDs when used on the master port.
355        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    /// Sends 0-valued bits over the port for a set duration
368    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    /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the
377    /// same serial connection. Please note that if you want a real asynchronous serial port you
378    /// should look at [mio-serial](https://crates.io/crates/mio-serial) or
379    /// [tokio-serial](https://crates.io/crates/tokio-serial).
380    ///
381    /// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since
382    /// the settings are cached on a per object basis, trying to modify them from two different
383    /// objects can cause some nasty behavior.
384    ///
385    /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead.
386    ///
387    /// # Errors
388    ///
389    /// This function returns an error if the serial port couldn't be cloned.
390    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        // Pull just the file descriptor out. We also prevent the destructor
418        // from being run by calling `mem::forget`. If we didn't do this, the
419        // port would be closed, which would make `into_raw_fd` unusable.
420        let TTYPort { fd, .. } = self;
421        mem::forget(self);
422        fd
423    }
424}
425
426/// Get the baud speed for a port from its file descriptor
427#[cfg(any(target_os = "ios", target_os = "macos"))]
428fn get_termios_speed(fd: RawFd) -> u32 {
429    let mut termios = MaybeUninit::uninit();
430    // TODO: Propagate error instead of panicking.
431    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        // TODO: If we fail to get the exclusive lock, this probably means that
443        // another process is using the port, and we should return an error,
444        // instead of using the port in non-exclusive mode.
445        //
446        // This will require a breaking change, as this method currently can't fail.
447
448        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            // It is not trivial to get the file path corresponding to a file descriptor.
455            // We'll punt on it and set it to `None` here.
456            port_name: None,
457            // It's not guaranteed that the baud rate in the `termios` struct is correct, as
458            // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value,
459            // but extract that value anyways as a best-guess of the actual baud rate.
460            #[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                    // Retry flushing. But only up to the ports timeout for not retrying
492                    // indefinitely in case that it gets interrupted again.
493                    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    /// Returns the port's baud rate
514    ///
515    /// On some platforms this will be the actual device baud rate, which may differ from the
516    /// desired baud rate.
517    #[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    /// Returns the port's baud rate
533    ///
534    /// On some platforms this will be the actual device baud rate, which may differ from the
535    /// desired baud rate.
536    #[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    /// Returns the port's baud rate
554    ///
555    /// On some platforms this will be the actual device baud rate, which may differ from the
556    /// desired baud rate.
557    #[cfg(any(target_os = "ios", target_os = "macos"))]
558    fn baud_rate(&self) -> Result<u32> {
559        Ok(self.baud_rate)
560    }
561
562    /// Returns the port's baud rate
563    ///
564    /// On some platforms this will be the actual device baud rate, which may differ from the
565    /// desired baud rate.
566    #[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    // Mac OS needs special logic for setting arbitrary baud rates.
689    #[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    // `master` must be used here as Dropping it causes slave to be deleted by the OS.
800    // TODO: Convert this to a statement-level attribute once
801    //       https://github.com/rust-lang/rust/issues/15701 is on stable.
802    // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe
803    #![allow(unused_variables)]
804    let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
805
806    // First test with the master
807    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    // And then the slave
816    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}