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