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 let slave = unsafe { File::from_raw_fd(slave_fd) };
116
117 (master, slave)
118 }
119
120 /// Test opening a master/slave PTTY pair
121 ///
122 /// This uses a common `open_ptty_pair` because much of these functions aren't useful by
123 /// themselves. So for this test we perform the basic act of getting a file handle for a
124 /// master/slave PTTY pair, then just sanity-check the raw values.
125 #[test]
test_open_ptty_pair()126 fn test_open_ptty_pair() {
127 let (master, slave) = open_ptty_pair();
128 assert!(master.as_raw_fd() > 0);
129 assert!(slave.as_raw_fd() > 0);
130 }
131
132 /// Put the terminal in raw mode.
make_raw(fd: RawFd)133 fn make_raw(fd: RawFd) {
134 let mut termios = tcgetattr(fd).unwrap();
135 cfmakeraw(&mut termios);
136 tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap();
137 }
138
139 /// Test `io::Read` on the PTTY master
140 #[test]
test_read_ptty_pair()141 fn test_read_ptty_pair() {
142 let (mut master, mut slave) = open_ptty_pair();
143 make_raw(slave.as_raw_fd());
144
145 let mut buf = [0u8; 5];
146 slave.write_all(b"hello").unwrap();
147 master.read_exact(&mut buf).unwrap();
148 assert_eq!(&buf, b"hello");
149 }
150
151 /// Test `io::Write` on the PTTY master
152 #[test]
test_write_ptty_pair()153 fn test_write_ptty_pair() {
154 let (mut master, mut slave) = open_ptty_pair();
155 make_raw(slave.as_raw_fd());
156
157 let mut buf = [0u8; 5];
158 master.write_all(b"adios").unwrap();
159 slave.read_exact(&mut buf).unwrap();
160 assert_eq!(&buf, b"adios");
161 }
162
163 #[test]
test_openpty()164 fn test_openpty() {
165 // openpty uses ptname(3) internally
166 let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
167
168 let pty = openpty(None, None).unwrap();
169 assert!(pty.master > 0);
170 assert!(pty.slave > 0);
171
172 // Writing to one should be readable on the other one
173 let string = "foofoofoo\n";
174 let mut buf = [0u8; 10];
175 write(pty.master, string.as_bytes()).unwrap();
176 crate::read_exact(pty.slave, &mut buf);
177
178 assert_eq!(&buf, string.as_bytes());
179
180 // Read the echo as well
181 let echoed_string = "foofoofoo\r\n";
182 let mut buf = [0u8; 11];
183 crate::read_exact(pty.master, &mut buf);
184 assert_eq!(&buf, echoed_string.as_bytes());
185
186 let string2 = "barbarbarbar\n";
187 let echoed_string2 = "barbarbarbar\r\n";
188 let mut buf = [0u8; 14];
189 write(pty.slave, string2.as_bytes()).unwrap();
190 crate::read_exact(pty.master, &mut buf);
191
192 assert_eq!(&buf, echoed_string2.as_bytes());
193
194 close(pty.master).unwrap();
195 close(pty.slave).unwrap();
196 }
197
198 #[test]
test_openpty_with_termios()199 fn test_openpty_with_termios() {
200 // openpty uses ptname(3) internally
201 let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
202
203 // Open one pty to get attributes for the second one
204 let mut termios = {
205 let pty = openpty(None, None).unwrap();
206 assert!(pty.master > 0);
207 assert!(pty.slave > 0);
208 let termios = tcgetattr(pty.slave).unwrap();
209 close(pty.master).unwrap();
210 close(pty.slave).unwrap();
211 termios
212 };
213 // Make sure newlines are not transformed so the data is preserved when sent.
214 termios.output_flags.remove(OutputFlags::ONLCR);
215
216 let pty = openpty(None, &termios).unwrap();
217 // Must be valid file descriptors
218 assert!(pty.master > 0);
219 assert!(pty.slave > 0);
220
221 // Writing to one should be readable on the other one
222 let string = "foofoofoo\n";
223 let mut buf = [0u8; 10];
224 write(pty.master, string.as_bytes()).unwrap();
225 crate::read_exact(pty.slave, &mut buf);
226
227 assert_eq!(&buf, string.as_bytes());
228
229 // read the echo as well
230 let echoed_string = "foofoofoo\n";
231 crate::read_exact(pty.master, &mut buf);
232 assert_eq!(&buf, echoed_string.as_bytes());
233
234 let string2 = "barbarbarbar\n";
235 let echoed_string2 = "barbarbarbar\n";
236 let mut buf = [0u8; 13];
237 write(pty.slave, string2.as_bytes()).unwrap();
238 crate::read_exact(pty.master, &mut buf);
239
240 assert_eq!(&buf, echoed_string2.as_bytes());
241
242 close(pty.master).unwrap();
243 close(pty.slave).unwrap();
244 }
245
246 #[test]
test_forkpty()247 fn test_forkpty() {
248 use nix::unistd::ForkResult::*;
249 use nix::sys::signal::*;
250 use nix::sys::wait::wait;
251 // forkpty calls openpty which uses ptname(3) internally.
252 let _m0 = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
253 // forkpty spawns a child process
254 let _m1 = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
255
256 let string = "naninani\n";
257 let echoed_string = "naninani\r\n";
258 let pty = forkpty(None, None).unwrap();
259 match pty.fork_result {
260 Child => {
261 write(STDOUT_FILENO, string.as_bytes()).unwrap();
262 pause(); // we need the child to stay alive until the parent calls read
263 unsafe { _exit(0); }
264 },
265 Parent { child } => {
266 let mut buf = [0u8; 10];
267 assert!(child.as_raw() > 0);
268 crate::read_exact(pty.master, &mut buf);
269 kill(child, SIGTERM).unwrap();
270 wait().unwrap(); // keep other tests using generic wait from getting our child
271 assert_eq!(&buf, echoed_string.as_bytes());
272 close(pty.master).unwrap();
273 },
274 }
275 }
276