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