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