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