1 use std::fs::File;
2 use std::io::{stdout, Read, Write};
3 use std::os::unix::prelude::*;
4 use std::path::Path;
5
6 use libc::_exit;
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(linux_android)]
test_ptsname_equivalence()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(linux_android)]
test_ptsname_copy()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(linux_android)]
test_ptsname_r_copy()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(linux_android)]
test_ptsname_unique()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
open_ptty_pair() -> (PtyMaster, File)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(solarish)]
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]
test_open_ptty_pair()133 fn test_open_ptty_pair() {
134 let (_, _) = open_ptty_pair();
135 }
136
137 /// Put the terminal in raw mode.
make_raw<Fd: AsFd>(fd: Fd)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]
test_read_ptty_pair()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]
test_write_ptty_pair()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]
test_openpty()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, 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, 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]
test_openpty_with_termios()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, 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, 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]
test_forkpty()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(), 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