1 | use std::fs::File; |
2 | use std::io::{Read, Write}; |
3 | use std::os::unix::prelude::*; |
4 | use std::path::Path; |
5 | |
6 | use libc::{_exit, STDOUT_FILENO}; |
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 (any(target_os = "android" , target_os = "linux" ))] |
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 (any(target_os = "android" , target_os = "linux" ))] |
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 (any(target_os = "android" , target_os = "linux" ))] |
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 (any(target_os = "android" , target_os = "linux" ))] |
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 |
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 (target_os = "illumos" )] |
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] |
133 | fn test_open_ptty_pair() { |
134 | let (_, _) = open_ptty_pair(); |
135 | } |
136 | |
137 | /// Put the terminal in raw mode. |
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] |
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] |
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] |
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.as_raw_fd(), 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.as_raw_fd(), 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] |
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.as_raw_fd(), 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.as_raw_fd(), 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] |
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_FILENO, 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 | |