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