• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::os::unix::prelude::*;
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::{close, read, write};
9 
10 /// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s
write_all(f: RawFd, buf: &[u8])11 fn write_all(f: RawFd, buf: &[u8]) {
12     let mut len = 0;
13     while len < buf.len() {
14         len += write(f, &buf[len..]).unwrap();
15     }
16 }
17 
18 // Test tcgetattr on a terminal
19 #[test]
test_tcgetattr_pty()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     close(pty.master).expect("closing the master failed");
27     close(pty.slave).expect("closing the slave failed");
28 }
29 
30 // Test tcgetattr on something that isn't a terminal
31 #[test]
test_tcgetattr_enotty()32 fn test_tcgetattr_enotty() {
33     let file = tempfile().unwrap();
34     assert_eq!(
35         termios::tcgetattr(file.as_raw_fd()).err(),
36         Some(Errno::ENOTTY)
37     );
38 }
39 
40 // Test tcgetattr on an invalid file descriptor
41 #[test]
test_tcgetattr_ebadf()42 fn test_tcgetattr_ebadf() {
43     assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF));
44 }
45 
46 // Test modifying output flags
47 #[test]
test_output_flags()48 fn test_output_flags() {
49     // openpty uses ptname(3) internally
50     let _m = crate::PTSNAME_MTX.lock();
51 
52     // Open one pty to get attributes for the second one
53     let mut termios = {
54         let pty = openpty(None, None).expect("openpty failed");
55         assert!(pty.master > 0);
56         assert!(pty.slave > 0);
57         let termios = tcgetattr(pty.slave).expect("tcgetattr failed");
58         close(pty.master).unwrap();
59         close(pty.slave).unwrap();
60         termios
61     };
62 
63     // Make sure postprocessing '\r' isn't specified by default or this test is useless.
64     assert!(!termios
65         .output_flags
66         .contains(OutputFlags::OPOST | OutputFlags::OCRNL));
67 
68     // Specify that '\r' characters should be transformed to '\n'
69     // OPOST is specified to enable post-processing
70     termios
71         .output_flags
72         .insert(OutputFlags::OPOST | OutputFlags::OCRNL);
73 
74     // Open a pty
75     let pty = openpty(None, &termios).unwrap();
76     assert!(pty.master > 0);
77     assert!(pty.slave > 0);
78 
79     // Write into the master
80     let string = "foofoofoo\r";
81     write_all(pty.master, string.as_bytes());
82 
83     // Read from the slave verifying that the output has been properly transformed
84     let mut buf = [0u8; 10];
85     crate::read_exact(pty.slave, &mut buf);
86     let transformed_string = "foofoofoo\n";
87     close(pty.master).unwrap();
88     close(pty.slave).unwrap();
89     assert_eq!(&buf, transformed_string.as_bytes());
90 }
91 
92 // Test modifying local flags
93 #[test]
test_local_flags()94 fn test_local_flags() {
95     // openpty uses ptname(3) internally
96     let _m = crate::PTSNAME_MTX.lock();
97 
98     // Open one pty to get attributes for the second one
99     let mut termios = {
100         let pty = openpty(None, None).unwrap();
101         assert!(pty.master > 0);
102         assert!(pty.slave > 0);
103         let termios = tcgetattr(pty.slave).unwrap();
104         close(pty.master).unwrap();
105         close(pty.slave).unwrap();
106         termios
107     };
108 
109     // Make sure echo is specified by default or this test is useless.
110     assert!(termios.local_flags.contains(LocalFlags::ECHO));
111 
112     // Disable local echo
113     termios.local_flags.remove(LocalFlags::ECHO);
114 
115     // Open a new pty with our modified termios settings
116     let pty = openpty(None, &termios).unwrap();
117     assert!(pty.master > 0);
118     assert!(pty.slave > 0);
119 
120     // Set the master is in nonblocking mode or reading will never return.
121     let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap();
122     let new_flags =
123         fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
124     fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap();
125 
126     // Write into the master
127     let string = "foofoofoo\r";
128     write_all(pty.master, string.as_bytes());
129 
130     // Try to read from the master, which should not have anything as echoing was disabled.
131     let mut buf = [0u8; 10];
132     let read = read(pty.master, &mut buf).unwrap_err();
133     close(pty.master).unwrap();
134     close(pty.slave).unwrap();
135     assert_eq!(read, Errno::EAGAIN);
136 }
137