1 use std::fs::File;
2 use std::io::{Read, Write};
3 use std::path::Path;
4 use std::os::unix::prelude::*;
5 use tempfile::tempfile;
6 
7 use libc::{_exit, STDOUT_FILENO};
8 use nix::fcntl::{OFlag, open};
9 use nix::pty::*;
10 use nix::sys::stat;
11 use nix::sys::termios::*;
12 use nix::unistd::{write, close, pause};
13 
14 /// Regression test for Issue #659
15 /// This is the correct way to explicitly close a `PtyMaster`
16 #[test]
test_explicit_close()17 fn test_explicit_close() {
18     let mut f = {
19         let m = posix_openpt(OFlag::O_RDWR).unwrap();
20         close(m.into_raw_fd()).unwrap();
21         tempfile().unwrap()
22     };
23     // This should work.  But if there's been a double close, then it will
24     // return EBADF
25     f.write_all(b"whatever").unwrap();
26 }
27 
28 /// Test equivalence of `ptsname` and `ptsname_r`
29 #[test]
30 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_equivalence()31 fn test_ptsname_equivalence() {
32     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
33 
34     // Open a new PTTY master
35     let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
36     assert!(master_fd.as_raw_fd() > 0);
37 
38     // Get the name of the slave
39     let slave_name = unsafe { ptsname(&master_fd) }.unwrap() ;
40     let slave_name_r = ptsname_r(&master_fd).unwrap();
41     assert_eq!(slave_name, slave_name_r);
42 }
43 
44 /// Test data copying of `ptsname`
45 // TODO need to run in a subprocess, since ptsname is non-reentrant
46 #[test]
47 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_copy()48 fn test_ptsname_copy() {
49     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
50 
51     // Open a new PTTY master
52     let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
53     assert!(master_fd.as_raw_fd() > 0);
54 
55     // Get the name of the slave
56     let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap();
57     let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap();
58     assert_eq!(slave_name1, slave_name2);
59     // Also make sure that the string was actually copied and they point to different parts of
60     // memory.
61     assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
62 }
63 
64 /// Test data copying of `ptsname_r`
65 #[test]
66 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_r_copy()67 fn test_ptsname_r_copy() {
68     // Open a new PTTY master
69     let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
70     assert!(master_fd.as_raw_fd() > 0);
71 
72     // Get the name of the slave
73     let slave_name1 = ptsname_r(&master_fd).unwrap();
74     let slave_name2 = ptsname_r(&master_fd).unwrap();
75     assert_eq!(slave_name1, slave_name2);
76     assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
77 }
78 
79 /// Test that `ptsname` returns different names for different devices
80 #[test]
81 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_unique()82 fn test_ptsname_unique() {
83     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
84 
85     // Open a new PTTY master
86     let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap();
87     assert!(master1_fd.as_raw_fd() > 0);
88 
89     // Open a second PTTY master
90     let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap();
91     assert!(master2_fd.as_raw_fd() > 0);
92 
93     // Get the name of the slave
94     let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap();
95     let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap();
96     assert!(slave_name1 != slave_name2);
97 }
98 
99 /// Common setup for testing PTTY pairs
open_ptty_pair() -> (PtyMaster, File)100 fn open_ptty_pair() -> (PtyMaster, File) {
101     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
102 
103     // Open a new PTTY master
104     let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
105 
106     // Allow a slave to be generated for it
107     grantpt(&master).expect("grantpt failed");
108     unlockpt(&master).expect("unlockpt failed");
109 
110     // Get the name of the slave
111     let slave_name = unsafe { ptsname(&master) }.expect("ptsname failed");
112 
113     // Open the slave device
114     let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()).unwrap();
115     let slave = unsafe { File::from_raw_fd(slave_fd) };
116 
117     (master, slave)
118 }
119 
120 /// Test opening a master/slave PTTY pair
121 ///
122 /// This uses a common `open_ptty_pair` because much of these functions aren't useful by
123 /// themselves. So for this test we perform the basic act of getting a file handle for a
124 /// master/slave PTTY pair, then just sanity-check the raw values.
125 #[test]
test_open_ptty_pair()126 fn test_open_ptty_pair() {
127     let (master, slave) = open_ptty_pair();
128     assert!(master.as_raw_fd() > 0);
129     assert!(slave.as_raw_fd() > 0);
130 }
131 
132 /// Put the terminal in raw mode.
make_raw(fd: RawFd)133 fn make_raw(fd: RawFd) {
134     let mut termios = tcgetattr(fd).unwrap();
135     cfmakeraw(&mut termios);
136     tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap();
137 }
138 
139 /// Test `io::Read` on the PTTY master
140 #[test]
test_read_ptty_pair()141 fn test_read_ptty_pair() {
142     let (mut master, mut slave) = open_ptty_pair();
143     make_raw(slave.as_raw_fd());
144 
145     let mut buf = [0u8; 5];
146     slave.write_all(b"hello").unwrap();
147     master.read_exact(&mut buf).unwrap();
148     assert_eq!(&buf, b"hello");
149 }
150 
151 /// Test `io::Write` on the PTTY master
152 #[test]
test_write_ptty_pair()153 fn test_write_ptty_pair() {
154     let (mut master, mut slave) = open_ptty_pair();
155     make_raw(slave.as_raw_fd());
156 
157     let mut buf = [0u8; 5];
158     master.write_all(b"adios").unwrap();
159     slave.read_exact(&mut buf).unwrap();
160     assert_eq!(&buf, b"adios");
161 }
162 
163 #[test]
test_openpty()164 fn test_openpty() {
165     // openpty uses ptname(3) internally
166     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
167 
168     let pty = openpty(None, None).unwrap();
169     assert!(pty.master > 0);
170     assert!(pty.slave > 0);
171 
172     // Writing to one should be readable on the other one
173     let string = "foofoofoo\n";
174     let mut buf = [0u8; 10];
175     write(pty.master, string.as_bytes()).unwrap();
176     crate::read_exact(pty.slave, &mut buf);
177 
178     assert_eq!(&buf, string.as_bytes());
179 
180     // Read the echo as well
181     let echoed_string = "foofoofoo\r\n";
182     let mut buf = [0u8; 11];
183     crate::read_exact(pty.master, &mut buf);
184     assert_eq!(&buf, echoed_string.as_bytes());
185 
186     let string2 = "barbarbarbar\n";
187     let echoed_string2 = "barbarbarbar\r\n";
188     let mut buf = [0u8; 14];
189     write(pty.slave, string2.as_bytes()).unwrap();
190     crate::read_exact(pty.master, &mut buf);
191 
192     assert_eq!(&buf, echoed_string2.as_bytes());
193 
194     close(pty.master).unwrap();
195     close(pty.slave).unwrap();
196 }
197 
198 #[test]
test_openpty_with_termios()199 fn test_openpty_with_termios() {
200     // openpty uses ptname(3) internally
201     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
202 
203     // Open one pty to get attributes for the second one
204     let mut termios = {
205         let pty = openpty(None, None).unwrap();
206         assert!(pty.master > 0);
207         assert!(pty.slave > 0);
208         let termios = tcgetattr(pty.slave).unwrap();
209         close(pty.master).unwrap();
210         close(pty.slave).unwrap();
211         termios
212     };
213     // Make sure newlines are not transformed so the data is preserved when sent.
214     termios.output_flags.remove(OutputFlags::ONLCR);
215 
216     let pty = openpty(None, &termios).unwrap();
217     // Must be valid file descriptors
218     assert!(pty.master > 0);
219     assert!(pty.slave > 0);
220 
221     // Writing to one should be readable on the other one
222     let string = "foofoofoo\n";
223     let mut buf = [0u8; 10];
224     write(pty.master, string.as_bytes()).unwrap();
225     crate::read_exact(pty.slave, &mut buf);
226 
227     assert_eq!(&buf, string.as_bytes());
228 
229     // read the echo as well
230     let echoed_string = "foofoofoo\n";
231     crate::read_exact(pty.master, &mut buf);
232     assert_eq!(&buf, echoed_string.as_bytes());
233 
234     let string2 = "barbarbarbar\n";
235     let echoed_string2 = "barbarbarbar\n";
236     let mut buf = [0u8; 13];
237     write(pty.slave, string2.as_bytes()).unwrap();
238     crate::read_exact(pty.master, &mut buf);
239 
240     assert_eq!(&buf, echoed_string2.as_bytes());
241 
242     close(pty.master).unwrap();
243     close(pty.slave).unwrap();
244 }
245 
246 #[test]
test_forkpty()247 fn test_forkpty() {
248     use nix::unistd::ForkResult::*;
249     use nix::sys::signal::*;
250     use nix::sys::wait::wait;
251     // forkpty calls openpty which uses ptname(3) internally.
252     let _m0 = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
253     // forkpty spawns a child process
254     let _m1 = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
255 
256     let string = "naninani\n";
257     let echoed_string = "naninani\r\n";
258     let pty = forkpty(None, None).unwrap();
259     match pty.fork_result {
260         Child => {
261             write(STDOUT_FILENO, string.as_bytes()).unwrap();
262             pause();  // we need the child to stay alive until the parent calls read
263             unsafe { _exit(0); }
264         },
265         Parent { child } => {
266             let mut buf = [0u8; 10];
267             assert!(child.as_raw() > 0);
268             crate::read_exact(pty.master, &mut buf);
269             kill(child, SIGTERM).unwrap();
270             wait().unwrap(); // keep other tests using generic wait from getting our child
271             assert_eq!(&buf, echoed_string.as_bytes());
272             close(pty.master).unwrap();
273         },
274     }
275 }
276