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 
116     #[cfg(target_os = "illumos")]
117     {
118         use libc::{ioctl, I_FIND, I_PUSH};
119 
120         // On illumos systems, as per pts(7D), one must push STREAMS modules
121         // after opening a device path returned from ptsname().
122         let ptem = b"ptem\0";
123         let ldterm = b"ldterm\0";
124         let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) };
125         if r < 0 {
126             panic!("I_FIND failure");
127         } else if r == 0 {
128             if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 {
129                 panic!("I_PUSH ptem failure");
130             }
131             if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 {
132                 panic!("I_PUSH ldterm failure");
133             }
134         }
135     }
136 
137     let slave = unsafe { File::from_raw_fd(slave_fd) };
138 
139     (master, slave)
140 }
141 
142 /// Test opening a master/slave PTTY pair
143 ///
144 /// This uses a common `open_ptty_pair` because much of these functions aren't useful by
145 /// themselves. So for this test we perform the basic act of getting a file handle for a
146 /// master/slave PTTY pair, then just sanity-check the raw values.
147 #[test]
test_open_ptty_pair()148 fn test_open_ptty_pair() {
149     let (master, slave) = open_ptty_pair();
150     assert!(master.as_raw_fd() > 0);
151     assert!(slave.as_raw_fd() > 0);
152 }
153 
154 /// Put the terminal in raw mode.
make_raw(fd: RawFd)155 fn make_raw(fd: RawFd) {
156     let mut termios = tcgetattr(fd).unwrap();
157     cfmakeraw(&mut termios);
158     tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap();
159 }
160 
161 /// Test `io::Read` on the PTTY master
162 #[test]
test_read_ptty_pair()163 fn test_read_ptty_pair() {
164     let (mut master, mut slave) = open_ptty_pair();
165     make_raw(slave.as_raw_fd());
166 
167     let mut buf = [0u8; 5];
168     slave.write_all(b"hello").unwrap();
169     master.read_exact(&mut buf).unwrap();
170     assert_eq!(&buf, b"hello");
171 }
172 
173 /// Test `io::Write` on the PTTY master
174 #[test]
test_write_ptty_pair()175 fn test_write_ptty_pair() {
176     let (mut master, mut slave) = open_ptty_pair();
177     make_raw(slave.as_raw_fd());
178 
179     let mut buf = [0u8; 5];
180     master.write_all(b"adios").unwrap();
181     slave.read_exact(&mut buf).unwrap();
182     assert_eq!(&buf, b"adios");
183 }
184 
185 #[test]
test_openpty()186 fn test_openpty() {
187     // openpty uses ptname(3) internally
188     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
189 
190     let pty = openpty(None, None).unwrap();
191     assert!(pty.master > 0);
192     assert!(pty.slave > 0);
193 
194     // Writing to one should be readable on the other one
195     let string = "foofoofoo\n";
196     let mut buf = [0u8; 10];
197     write(pty.master, string.as_bytes()).unwrap();
198     crate::read_exact(pty.slave, &mut buf);
199 
200     assert_eq!(&buf, string.as_bytes());
201 
202     // Read the echo as well
203     let echoed_string = "foofoofoo\r\n";
204     let mut buf = [0u8; 11];
205     crate::read_exact(pty.master, &mut buf);
206     assert_eq!(&buf, echoed_string.as_bytes());
207 
208     let string2 = "barbarbarbar\n";
209     let echoed_string2 = "barbarbarbar\r\n";
210     let mut buf = [0u8; 14];
211     write(pty.slave, string2.as_bytes()).unwrap();
212     crate::read_exact(pty.master, &mut buf);
213 
214     assert_eq!(&buf, echoed_string2.as_bytes());
215 
216     close(pty.master).unwrap();
217     close(pty.slave).unwrap();
218 }
219 
220 #[test]
test_openpty_with_termios()221 fn test_openpty_with_termios() {
222     // openpty uses ptname(3) internally
223     let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
224 
225     // Open one pty to get attributes for the second one
226     let mut termios = {
227         let pty = openpty(None, None).unwrap();
228         assert!(pty.master > 0);
229         assert!(pty.slave > 0);
230         let termios = tcgetattr(pty.slave).unwrap();
231         close(pty.master).unwrap();
232         close(pty.slave).unwrap();
233         termios
234     };
235     // Make sure newlines are not transformed so the data is preserved when sent.
236     termios.output_flags.remove(OutputFlags::ONLCR);
237 
238     let pty = openpty(None, &termios).unwrap();
239     // Must be valid file descriptors
240     assert!(pty.master > 0);
241     assert!(pty.slave > 0);
242 
243     // Writing to one should be readable on the other one
244     let string = "foofoofoo\n";
245     let mut buf = [0u8; 10];
246     write(pty.master, string.as_bytes()).unwrap();
247     crate::read_exact(pty.slave, &mut buf);
248 
249     assert_eq!(&buf, string.as_bytes());
250 
251     // read the echo as well
252     let echoed_string = "foofoofoo\n";
253     crate::read_exact(pty.master, &mut buf);
254     assert_eq!(&buf, echoed_string.as_bytes());
255 
256     let string2 = "barbarbarbar\n";
257     let echoed_string2 = "barbarbarbar\n";
258     let mut buf = [0u8; 13];
259     write(pty.slave, string2.as_bytes()).unwrap();
260     crate::read_exact(pty.master, &mut buf);
261 
262     assert_eq!(&buf, echoed_string2.as_bytes());
263 
264     close(pty.master).unwrap();
265     close(pty.slave).unwrap();
266 }
267 
268 #[test]
test_forkpty()269 fn test_forkpty() {
270     use nix::unistd::ForkResult::*;
271     use nix::sys::signal::*;
272     use nix::sys::wait::wait;
273     // forkpty calls openpty which uses ptname(3) internally.
274     let _m0 = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
275     // forkpty spawns a child process
276     let _m1 = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
277 
278     let string = "naninani\n";
279     let echoed_string = "naninani\r\n";
280     let pty = unsafe {
281         forkpty(None, None).unwrap()
282     };
283     match pty.fork_result {
284         Child => {
285             write(STDOUT_FILENO, string.as_bytes()).unwrap();
286             pause();  // we need the child to stay alive until the parent calls read
287             unsafe { _exit(0); }
288         },
289         Parent { child } => {
290             let mut buf = [0u8; 10];
291             assert!(child.as_raw() > 0);
292             crate::read_exact(pty.master, &mut buf);
293             kill(child, SIGTERM).unwrap();
294             wait().unwrap(); // keep other tests using generic wait from getting our child
295             assert_eq!(&buf, echoed_string.as_bytes());
296             close(pty.master).unwrap();
297         },
298     }
299 }
300