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