serialport/posix/
tty.rs

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
17/// Convenience method for removing exclusive access from
18/// a fd and closing it.
19fn close(fd: RawFd) {
20    // remove exclusive access
21    let _ = ioctl::tiocnxcl(fd);
22
23    // On Linux and BSD, we don't need to worry about return
24    // type as EBADF means the fd was never open or is already closed
25    //
26    // Linux and BSD guarantee that for any other error code the
27    // fd is already closed, though MacOSX does not.
28    //
29    // close() also should never be retried, and the error code
30    // in most cases in purely informative
31    let _ = unistd::close(fd);
32}
33
34/// A serial port implementation for POSIX TTY ports
35///
36/// The port will be closed when the value is dropped. This struct
37/// should not be instantiated directly by using `TTYPort::open()`.
38/// Instead, use the cross-platform `serialport::new()`. Example:
39///
40/// ```no_run
41/// let mut port = serialport::new("/dev/ttyS0", 115200).open().expect("Unable to open");
42/// # let _ = &mut port;
43/// ```
44///
45/// Note: on macOS, when connecting to a pseudo-terminal (`pty` opened via
46/// `posix_openpt`), the `baud_rate` should be set to 0; this will be used to
47/// explicitly _skip_ an attempt to set the baud rate of the file descriptor
48/// that would otherwise happen via an `ioctl` command.
49///
50/// ```
51/// use serialport::{TTYPort, SerialPort};
52///
53/// let (mut master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair");
54/// # let _ = &mut master;
55/// # let _ = &mut slave;
56/// // ... elsewhere
57/// let mut port = TTYPort::open(&serialport::new(slave.name().unwrap(), 0)).expect("Unable to open");
58/// # let _ = &mut port;
59/// ```
60#[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/// Specifies the duration of a transmission break
71#[derive(Clone, Copy, Debug)]
72pub enum BreakDuration {
73    /// 0.25-0.5s
74    Short,
75    /// Specifies a break duration that is platform-dependent
76    Arbitrary(std::num::NonZeroI32),
77}
78
79/// Wrapper for RawFd to assure that it's properly closed,
80/// even if the enclosing function exits early.
81///
82/// This is similar to the (nightly-only) std::os::unix::io::OwnedFd.
83struct 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    /// Opens a TTY device as a serial port.
101    ///
102    /// `path` should be the path to a TTY device, e.g., `/dev/ttyS0`.
103    ///
104    /// Ports are opened in exclusive mode by default. If this is undesirable
105    /// behavior, use `TTYPort::set_exclusive(false)`.
106    ///
107    /// If the port settings differ from the default settings, characters received
108    /// before the new settings become active may be garbled. To remove those
109    /// from the receive buffer, call `TTYPort::clear(ClearBuffer::Input)`.
110    ///
111    /// ## Errors
112    ///
113    /// * `NoDevice` if the device could not be opened. This could indicate that
114    ///    the device is already in use.
115    /// * `InvalidInput` if `path` is not a valid device name.
116    /// * `Io` for any other error while opening or initializing the device.
117    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        // Try to claim exclusive access to the port. This is performed even
129        // if the port will later be set as non-exclusive, in order to respect
130        // other applications that may have an exclusive port lock.
131        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        // setup TTY for binary serial port access
138        // Enable reading from the port and ignore all modem control lines
139        termios.c_cflag |= libc::CREAD | libc::CLOCAL;
140        // Enable raw mode which disables any implicit processing of the input or output data streams
141        // This also sets no timeout period and a read will block until at least one character is
142        // available.
143        unsafe { cfmakeraw(&mut termios) };
144
145        // write settings to TTY
146        unsafe { tcsetattr(fd.0, libc::TCSANOW, &termios) };
147
148        // Read back settings from port and confirm they were applied correctly
149        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        // clear O_NONBLOCK flag
170        fcntl(fd.0, F_SETFL(nix::fcntl::OFlag::empty()))?;
171
172        // Configure the low-level port settings
173        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        // Return the final port object
186        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    /// Returns the exclusivity of the port
197    ///
198    /// If a port is exclusive, then trying to open the same device path again
199    /// will fail.
200    pub fn exclusive(&self) -> bool {
201        self.exclusive
202    }
203
204    /// Sets the exclusivity of the port
205    ///
206    /// If a port is exclusive, then trying to open the same device path again
207    /// will fail.
208    ///
209    /// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details.
210    ///
211    /// ## Errors
212    ///
213    /// * `Io` for any error while setting exclusivity for the port.
214    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    /// Create a pair of pseudo serial terminals
239    ///
240    /// ## Returns
241    /// Two connected `TTYPort` objects: `(master, slave)`
242    ///
243    /// ## Errors
244    /// Attempting any IO or parameter settings on the slave tty after the master
245    /// tty is closed will return errors.
246    ///
247    /// On some platforms manipulating the master port will fail and only
248    /// modifying the slave port is possible.
249    ///
250    /// ## Examples
251    ///
252    /// ```
253    /// use serialport::TTYPort;
254    ///
255    /// let (mut master, mut slave) = TTYPort::pair().unwrap();
256    ///
257    /// # let _ = &mut master;
258    /// # let _ = &mut slave;
259    /// ```
260    pub fn pair() -> Result<(Self, Self)> {
261        // Open the next free pty.
262        let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?;
263
264        // Grant access to the associated slave pty
265        nix::pty::grantpt(&next_pty_fd)?;
266
267        // Unlock the slave pty
268        nix::pty::unlockpt(&next_pty_fd)?;
269
270        // Get the path of the attached slave ptty
271        #[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        // Open the slave port
288        #[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        // Set the port to a raw state. Using these ports will not work without this.
297        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        // Manually construct the master port here because the
322        // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other
323        // BSDs when used on the master port.
324        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    /// Sends 0-valued bits over the port for a set duration
337    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    /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the
346    /// same serial connection. Please note that if you want a real asynchronous serial port you
347    /// should look at [mio-serial](https://crates.io/crates/mio-serial) or
348    /// [tokio-serial](https://crates.io/crates/tokio-serial).
349    ///
350    /// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since
351    /// the settings are cached on a per object basis, trying to modify them from two different
352    /// objects can cause some nasty behavior.
353    ///
354    /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead.
355    ///
356    /// # Errors
357    ///
358    /// This function returns an error if the serial port couldn't be cloned.
359    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        // Pull just the file descriptor out. We also prevent the destructor
387        // from being run by calling `mem::forget`. If we didn't do this, the
388        // port would be closed, which would make `into_raw_fd` unusable.
389        let TTYPort { fd, .. } = self;
390        mem::forget(self);
391        fd
392    }
393}
394
395/// Get the baud speed for a port from its file descriptor
396#[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            // It is not trivial to get the file path corresponding to a file descriptor.
413            // We'll punt on it and set it to `None` here.
414            port_name: None,
415            // It's not guaranteed that the baud rate in the `termios` struct is correct, as
416            // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value,
417            // but extract that value anyways as a best-guess of the actual baud rate.
418            #[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    /// Returns the port's baud rate
455    ///
456    /// On some platforms this will be the actual device baud rate, which may differ from the
457    /// desired baud rate.
458    #[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    /// Returns the port's baud rate
478    ///
479    /// On some platforms this will be the actual device baud rate, which may differ from the
480    /// desired baud rate.
481    #[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    /// Returns the port's baud rate
499    ///
500    /// On some platforms this will be the actual device baud rate, which may differ from the
501    /// desired baud rate.
502    #[cfg(any(target_os = "ios", target_os = "macos"))]
503    fn baud_rate(&self) -> Result<u32> {
504        Ok(self.baud_rate)
505    }
506
507    /// Returns the port's baud rate
508    ///
509    /// On some platforms this will be the actual device baud rate, which may differ from the
510    /// desired baud rate.
511    #[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    // Mac OS needs special logic for setting arbitrary baud rates.
638    #[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    // `master` must be used here as Dropping it causes slave to be deleted by the OS.
751    // TODO: Convert this to a statement-level attribute once
752    //       https://github.com/rust-lang/rust/issues/15701 is on stable.
753    // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe
754    #![allow(unused_variables)]
755    let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
756
757    // First test with the master
758    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    // And then the slave
767    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}