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