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::sys::wait::WaitStatus;
12 use nix::unistd::{pause, write};
13
14 /// Test equivalence of `ptsname` and `ptsname_r`
15 #[test]
16 #[cfg(linux_android)]
test_ptsname_equivalence()17 fn test_ptsname_equivalence() {
18 let _m = crate::PTSNAME_MTX.lock();
19
20 // Open a new PTY master
21 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
22 assert!(master_fd.as_raw_fd() > 0);
23 assert!(master_fd.as_fd().as_raw_fd() == master_fd.as_raw_fd());
24
25 // Get the name of the slave
26 let slave_name = unsafe { ptsname(&master_fd) }.unwrap();
27 let slave_name_r = ptsname_r(&master_fd).unwrap();
28 assert_eq!(slave_name, slave_name_r);
29 }
30
31 /// Test data copying of `ptsname`
32 // TODO need to run in a subprocess, since ptsname is non-reentrant
33 #[test]
34 #[cfg(linux_android)]
test_ptsname_copy()35 fn test_ptsname_copy() {
36 let _m = crate::PTSNAME_MTX.lock();
37
38 // Open a new PTTY master
39 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
40
41 // Get the name of the slave
42 let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap();
43 let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap();
44 assert_eq!(slave_name1, slave_name2);
45 // Also make sure that the string was actually copied and they point to different parts of
46 // memory.
47 assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr());
48 }
49
50 /// Test data copying of `ptsname_r`
51 #[test]
52 #[cfg(linux_android)]
test_ptsname_r_copy()53 fn test_ptsname_r_copy() {
54 // Open a new PTTY master
55 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
56
57 // Get the name of the slave
58 let slave_name1 = ptsname_r(&master_fd).unwrap();
59 let slave_name2 = ptsname_r(&master_fd).unwrap();
60 assert_eq!(slave_name1, slave_name2);
61 assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr());
62 }
63
64 /// Test that `ptsname` returns different names for different devices
65 #[test]
66 #[cfg(linux_android)]
test_ptsname_unique()67 fn test_ptsname_unique() {
68 let _m = crate::PTSNAME_MTX.lock();
69
70 // Open a new PTTY master
71 let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap();
72
73 // Open a second PTTY master
74 let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap();
75
76 // Get the name of the slave
77 let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap();
78 let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap();
79 assert_ne!(slave_name1, slave_name2);
80 }
81
82 /// Common setup for testing PTTY pairs
open_ptty_pair() -> (PtyMaster, File)83 fn open_ptty_pair() -> (PtyMaster, File) {
84 let _m = crate::PTSNAME_MTX.lock();
85
86 // Open a new PTTY master
87 let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
88
89 // Allow a slave to be generated for it
90 grantpt(&master).expect("grantpt failed");
91 unlockpt(&master).expect("unlockpt failed");
92
93 // Get the name of the slave
94 let slave_name = unsafe { ptsname(&master) }.expect("ptsname failed");
95
96 // Open the slave device
97 let slave_fd =
98 open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())
99 .unwrap();
100
101 #[cfg(solarish)]
102 // TODO: rewrite using ioctl!
103 #[allow(clippy::comparison_chain)]
104 {
105 use libc::{ioctl, I_FIND, I_PUSH};
106
107 // On illumos systems, as per pts(7D), one must push STREAMS modules
108 // after opening a device path returned from ptsname().
109 let ptem = b"ptem\0";
110 let ldterm = b"ldterm\0";
111 let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) };
112 if r < 0 {
113 panic!("I_FIND failure");
114 } else if r == 0 {
115 if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 {
116 panic!("I_PUSH ptem failure");
117 }
118 if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 {
119 panic!("I_PUSH ldterm failure");
120 }
121 }
122 }
123
124 let slave = unsafe { File::from_raw_fd(slave_fd) };
125
126 (master, slave)
127 }
128
129 /// Test opening a master/slave PTTY pair
130 ///
131 /// This uses a common `open_ptty_pair` because much of these functions aren't useful by
132 /// themselves. So for this test we perform the basic act of getting a file handle for a
133 /// master/slave PTTY pair.
134 #[test]
test_open_ptty_pair()135 fn test_open_ptty_pair() {
136 let (_, _) = open_ptty_pair();
137 }
138
139 /// Put the terminal in raw mode.
make_raw<Fd: AsFd>(fd: Fd)140 fn make_raw<Fd: AsFd>(fd: Fd) {
141 let mut termios = tcgetattr(&fd).unwrap();
142 cfmakeraw(&mut termios);
143 tcsetattr(&fd, SetArg::TCSANOW, &termios).unwrap();
144 }
145
146 /// Test `io::Read` on the PTTY master
147 #[test]
test_read_ptty_pair()148 fn test_read_ptty_pair() {
149 let (mut master, mut slave) = open_ptty_pair();
150 make_raw(&slave);
151
152 let mut buf = [0u8; 5];
153 slave.write_all(b"hello").unwrap();
154 master.read_exact(&mut buf).unwrap();
155 assert_eq!(&buf, b"hello");
156
157 let mut master = &master;
158 slave.write_all(b"hello").unwrap();
159 master.read_exact(&mut buf).unwrap();
160 assert_eq!(&buf, b"hello");
161 }
162
163 /// Test `io::Write` on the PTTY master
164 #[test]
test_write_ptty_pair()165 fn test_write_ptty_pair() {
166 let (mut master, mut slave) = open_ptty_pair();
167 make_raw(&slave);
168
169 let mut buf = [0u8; 5];
170 master.write_all(b"adios").unwrap();
171 slave.read_exact(&mut buf).unwrap();
172 assert_eq!(&buf, b"adios");
173
174 let mut master = &master;
175 master.write_all(b"adios").unwrap();
176 slave.read_exact(&mut buf).unwrap();
177 assert_eq!(&buf, b"adios");
178 }
179
180 #[test]
test_openpty()181 fn test_openpty() {
182 // openpty uses ptname(3) internally
183 let _m = crate::PTSNAME_MTX.lock();
184
185 let pty = openpty(None, None).unwrap();
186
187 // Writing to one should be readable on the other one
188 let string = "foofoofoo\n";
189 let mut buf = [0u8; 10];
190 write(&pty.master, string.as_bytes()).unwrap();
191 crate::read_exact(&pty.slave, &mut buf);
192
193 assert_eq!(&buf, string.as_bytes());
194
195 // Read the echo as well
196 let echoed_string = "foofoofoo\r\n";
197 let mut buf = [0u8; 11];
198 crate::read_exact(&pty.master, &mut buf);
199 assert_eq!(&buf, echoed_string.as_bytes());
200
201 let string2 = "barbarbarbar\n";
202 let echoed_string2 = "barbarbarbar\r\n";
203 let mut buf = [0u8; 14];
204 write(&pty.slave, string2.as_bytes()).unwrap();
205 crate::read_exact(&pty.master, &mut buf);
206
207 assert_eq!(&buf, echoed_string2.as_bytes());
208 }
209
210 #[test]
test_openpty_with_termios()211 fn test_openpty_with_termios() {
212 // openpty uses ptname(3) internally
213 let _m = crate::PTSNAME_MTX.lock();
214
215 // Open one pty to get attributes for the second one
216 let mut termios = {
217 let pty = openpty(None, None).unwrap();
218 tcgetattr(&pty.slave).unwrap()
219 };
220 // Make sure newlines are not transformed so the data is preserved when sent.
221 termios.output_flags.remove(OutputFlags::ONLCR);
222
223 let pty = openpty(None, &termios).unwrap();
224 // Must be valid file descriptors
225
226 // Writing to one should be readable on the other one
227 let string = "foofoofoo\n";
228 let mut buf = [0u8; 10];
229 write(&pty.master, string.as_bytes()).unwrap();
230 crate::read_exact(&pty.slave, &mut buf);
231
232 assert_eq!(&buf, string.as_bytes());
233
234 // read the echo as well
235 let echoed_string = "foofoofoo\n";
236 crate::read_exact(&pty.master, &mut buf);
237 assert_eq!(&buf, echoed_string.as_bytes());
238
239 let string2 = "barbarbarbar\n";
240 let echoed_string2 = "barbarbarbar\n";
241 let mut buf = [0u8; 13];
242 write(&pty.slave, string2.as_bytes()).unwrap();
243 crate::read_exact(&pty.master, &mut buf);
244
245 assert_eq!(&buf, echoed_string2.as_bytes());
246 }
247
248 #[test]
test_forkpty()249 fn test_forkpty() {
250 use nix::sys::signal::*;
251 use nix::sys::wait::wait;
252 // forkpty calls openpty which uses ptname(3) internally.
253 let _m0 = crate::PTSNAME_MTX.lock();
254 // forkpty spawns a child process
255 let _m1 = crate::FORK_MTX.lock();
256
257 let string = "naninani\n";
258 let echoed_string = "naninani\r\n";
259 let res = unsafe { forkpty(None, None).unwrap() };
260 match res {
261 ForkptyResult::Child => {
262 write(stdout(), string.as_bytes()).unwrap();
263 pause(); // we need the child to stay alive until the parent calls read
264 unsafe {
265 _exit(0);
266 }
267 }
268 ForkptyResult::Parent { child, master } => {
269 let mut buf = [0u8; 10];
270 assert!(child.as_raw() > 0);
271 crate::read_exact(&master, &mut buf);
272 kill(child, SIGTERM).unwrap();
273 let status = wait().unwrap(); // keep other tests using generic wait from getting our child
274 assert_eq!(status, WaitStatus::Signaled(child, SIGTERM, false));
275 assert_eq!(&buf, echoed_string.as_bytes());
276 }
277 }
278 }
279