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