1 | use std::os::unix::io::{AsFd, AsRawFd}; |
2 | use tempfile::tempfile; |
3 | |
4 | use nix::errno::Errno; |
5 | use nix::fcntl; |
6 | use nix::pty::openpty; |
7 | use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags}; |
8 | use nix::unistd::{read, write}; |
9 | |
10 | /// Helper function analogous to `std::io::Write::write_all`, but for `Fd`s |
11 | fn write_all<Fd: AsFd>(f: Fd, buf: &[u8]) { |
12 | let mut len = 0; |
13 | while len < buf.len() { |
14 | len += write(f.as_fd().as_raw_fd(), &buf[len..]).unwrap(); |
15 | } |
16 | } |
17 | |
18 | // Test tcgetattr on a terminal |
19 | #[test] |
20 | fn test_tcgetattr_pty() { |
21 | // openpty uses ptname(3) internally |
22 | let _m = crate::PTSNAME_MTX.lock(); |
23 | |
24 | let pty = openpty(None, None).expect("openpty failed" ); |
25 | termios::tcgetattr(&pty.slave).unwrap(); |
26 | } |
27 | |
28 | // Test tcgetattr on something that isn't a terminal |
29 | #[test] |
30 | fn test_tcgetattr_enotty() { |
31 | let file = tempfile().unwrap(); |
32 | assert_eq!(termios::tcgetattr(&file).err(), Some(Errno::ENOTTY)); |
33 | } |
34 | |
35 | // Test modifying output flags |
36 | #[test] |
37 | fn test_output_flags() { |
38 | // openpty uses ptname(3) internally |
39 | let _m = crate::PTSNAME_MTX.lock(); |
40 | |
41 | // Open one pty to get attributes for the second one |
42 | let mut termios = { |
43 | let pty = openpty(None, None).expect("openpty failed" ); |
44 | tcgetattr(&pty.slave).expect("tcgetattr failed" ) |
45 | }; |
46 | |
47 | // Make sure postprocessing '\r' isn't specified by default or this test is useless. |
48 | assert!(!termios |
49 | .output_flags |
50 | .contains(OutputFlags::OPOST | OutputFlags::OCRNL)); |
51 | |
52 | // Specify that '\r' characters should be transformed to '\n' |
53 | // OPOST is specified to enable post-processing |
54 | termios |
55 | .output_flags |
56 | .insert(OutputFlags::OPOST | OutputFlags::OCRNL); |
57 | |
58 | // Open a pty |
59 | let pty = openpty(None, &termios).unwrap(); |
60 | |
61 | // Write into the master |
62 | let string = "foofoofoo \r" ; |
63 | write_all(&pty.master, string.as_bytes()); |
64 | |
65 | // Read from the slave verifying that the output has been properly transformed |
66 | let mut buf = [0u8; 10]; |
67 | crate::read_exact(&pty.slave, &mut buf); |
68 | let transformed_string = "foofoofoo \n" ; |
69 | assert_eq!(&buf, transformed_string.as_bytes()); |
70 | } |
71 | |
72 | // Test modifying local flags |
73 | #[test] |
74 | fn test_local_flags() { |
75 | // openpty uses ptname(3) internally |
76 | let _m = crate::PTSNAME_MTX.lock(); |
77 | |
78 | // Open one pty to get attributes for the second one |
79 | let mut termios = { |
80 | let pty = openpty(None, None).unwrap(); |
81 | tcgetattr(&pty.slave).unwrap() |
82 | }; |
83 | |
84 | // Make sure echo is specified by default or this test is useless. |
85 | assert!(termios.local_flags.contains(LocalFlags::ECHO)); |
86 | |
87 | // Disable local echo |
88 | termios.local_flags.remove(LocalFlags::ECHO); |
89 | |
90 | // Open a new pty with our modified termios settings |
91 | let pty = openpty(None, &termios).unwrap(); |
92 | |
93 | // Set the master is in nonblocking mode or reading will never return. |
94 | let flags = fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_GETFL).unwrap(); |
95 | let new_flags = |
96 | fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK; |
97 | fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_SETFL(new_flags)).unwrap(); |
98 | |
99 | // Write into the master |
100 | let string = "foofoofoo \r" ; |
101 | write_all(&pty.master, string.as_bytes()); |
102 | |
103 | // Try to read from the master, which should not have anything as echoing was disabled. |
104 | let mut buf = [0u8; 10]; |
105 | let read = read(pty.master.as_raw_fd(), &mut buf).unwrap_err(); |
106 | assert_eq!(read, Errno::EAGAIN); |
107 | } |
108 | |