1use std::os::unix::prelude::*;
2use tempfile::tempfile;
3
4use nix::errno::Errno;
5use nix::fcntl;
6use nix::pty::openpty;
7use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags};
8use nix::unistd::{close, read, write};
9
10/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s
11fn write_all(f: RawFd, buf: &[u8]) {
12    let mut len = 0;
13    while len < buf.len() {
14        len += write(f, &buf[len..]).unwrap();
15    }
16}
17
18// Test tcgetattr on a terminal
19#[test]
20fn test_tcgetattr_pty() {
21    // openpty uses ptname(3) internally
22    let _m = crate::PTSNAME_MTX.lock();
23
24    let pty = openpty(None, None).expect("openpty failed");
25    termios::tcgetattr(pty.slave).unwrap();
26    close(pty.master).expect("closing the master failed");
27    close(pty.slave).expect("closing the slave failed");
28}
29
30// Test tcgetattr on something that isn't a terminal
31#[test]
32fn test_tcgetattr_enotty() {
33    let file = tempfile().unwrap();
34    assert_eq!(
35        termios::tcgetattr(file.as_raw_fd()).err(),
36        Some(Errno::ENOTTY)
37    );
38}
39
40// Test tcgetattr on an invalid file descriptor
41#[test]
42fn test_tcgetattr_ebadf() {
43    assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF));
44}
45
46// Test modifying output flags
47#[test]
48fn test_output_flags() {
49    // openpty uses ptname(3) internally
50    let _m = crate::PTSNAME_MTX.lock();
51
52    // Open one pty to get attributes for the second one
53    let mut termios = {
54        let pty = openpty(None, None).expect("openpty failed");
55        assert!(pty.master > 0);
56        assert!(pty.slave > 0);
57        let termios = tcgetattr(pty.slave).expect("tcgetattr failed");
58        close(pty.master).unwrap();
59        close(pty.slave).unwrap();
60        termios
61    };
62
63    // Make sure postprocessing '\r' isn't specified by default or this test is useless.
64    assert!(!termios
65        .output_flags
66        .contains(OutputFlags::OPOST | OutputFlags::OCRNL));
67
68    // Specify that '\r' characters should be transformed to '\n'
69    // OPOST is specified to enable post-processing
70    termios
71        .output_flags
72        .insert(OutputFlags::OPOST | OutputFlags::OCRNL);
73
74    // Open a pty
75    let pty = openpty(None, &termios).unwrap();
76    assert!(pty.master > 0);
77    assert!(pty.slave > 0);
78
79    // Write into the master
80    let string = "foofoofoo\r";
81    write_all(pty.master, string.as_bytes());
82
83    // Read from the slave verifying that the output has been properly transformed
84    let mut buf = [0u8; 10];
85    crate::read_exact(pty.slave, &mut buf);
86    let transformed_string = "foofoofoo\n";
87    close(pty.master).unwrap();
88    close(pty.slave).unwrap();
89    assert_eq!(&buf, transformed_string.as_bytes());
90}
91
92// Test modifying local flags
93#[test]
94fn test_local_flags() {
95    // openpty uses ptname(3) internally
96    let _m = crate::PTSNAME_MTX.lock();
97
98    // Open one pty to get attributes for the second one
99    let mut termios = {
100        let pty = openpty(None, None).unwrap();
101        assert!(pty.master > 0);
102        assert!(pty.slave > 0);
103        let termios = tcgetattr(pty.slave).unwrap();
104        close(pty.master).unwrap();
105        close(pty.slave).unwrap();
106        termios
107    };
108
109    // Make sure echo is specified by default or this test is useless.
110    assert!(termios.local_flags.contains(LocalFlags::ECHO));
111
112    // Disable local echo
113    termios.local_flags.remove(LocalFlags::ECHO);
114
115    // Open a new pty with our modified termios settings
116    let pty = openpty(None, &termios).unwrap();
117    assert!(pty.master > 0);
118    assert!(pty.slave > 0);
119
120    // Set the master is in nonblocking mode or reading will never return.
121    let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap();
122    let new_flags =
123        fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
124    fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap();
125
126    // Write into the master
127    let string = "foofoofoo\r";
128    write_all(pty.master, string.as_bytes());
129
130    // Try to read from the master, which should not have anything as echoing was disabled.
131    let mut buf = [0u8; 10];
132    let read = read(pty.master, &mut buf).unwrap_err();
133    close(pty.master).unwrap();
134    close(pty.slave).unwrap();
135    assert_eq!(read, Errno::EAGAIN);
136}
137