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