• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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