1use std::fs::File;
2use std::io::{Read, Write};
3use std::os::unix::prelude::*;
4use std::path::Path;
5
6use libc::{_exit, STDOUT_FILENO};
7use nix::fcntl::{open, OFlag};
8use nix::pty::*;
9use nix::sys::stat;
10use nix::sys::termios::*;
11use nix::unistd::{pause, write};
12
13/// Test equivalence of `ptsname` and `ptsname_r`
14#[test]
15#[cfg(any(target_os = "android", target_os = "linux"))]
16fn 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"))]
33fn 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"))]
51fn 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"))]
65fn 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
81fn 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]
133fn test_open_ptty_pair() {
134 let (_, _) = open_ptty_pair();
135}
136
137/// Put the terminal in raw mode.
138fn 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]
146fn 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]
163fn 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]
179fn 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]
209fn 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]
247fn 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