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